Sıfır elemanlı diziye ne gerek var?


122

Linux çekirdek kodunda anlayamadığım şu şeyi buldum.

 struct bts_action {
         u16 type;
         u16 size;
         u8 data[0];
 } __attribute__ ((packed));

Kod burada: http://lxr.free-electrons.com/source/include/linux/ti_wilink_st.h

Sıfır elemanlı bir veri dizisinin gerekliliği ve amacı nedir?


Sıfır uzunluklu diziler mi yoksa struct-hack etiketi mi olması gerektiğinden emin değilim ...
hippietrail

@hippietrail, çünkü çoğu zaman birisi bu yapının ne olduğunu sorduğunda, "esnek dizi üyesi" olarak adlandırıldığını bilmez. Olsaydı, cevabını kolayca bulabilirlerdi. Aksi halde soruyu bu şekilde etiketleyemezler. Bu yüzden böyle bir etiketimiz yok.
Shahbaz 13

10
Yeniden açmak için oy verin. Bunun bir kopya olmadığını kabul ediyorum, çünkü diğer gönderilerin hiçbiri, sıfır uzunluklu standart olmayan bir "struct hack" ve iyi tanımlanmış C99 özelliği esnek dizi üyesinin kombinasyonunu ele almıyor. Ayrıca, C programlama topluluğunun Linux çekirdeğinden gelen belirsiz kodlara biraz ışık tutmasının her zaman yararlı olduğunu düşünüyorum. Esasen pek çok insan, bilinmeyen nedenlerden ötürü Linux çekirdeğinin bir tür son teknoloji C kodu olduğu izlenimine sahip olduğundan. Gerçekte, hiçbir zaman bir C kanonu olarak görülmemesi gereken standart dışı istismarlarla dolu korkunç bir karmaşa olsa da.
Lundin 13

5
Kopya değil - ilk kez birinin bir soruyu gereksiz yere kapattığını görüyorum. Ayrıca bu sorunun SO Bilgi tabanına katkıda bulunduğunu düşünüyorum.
Aniket Inge 01

Yanıtlar:


139

Bu, iki kez aramak zorunda kalmadan malloc( kmallocbu durumda) değişken veri boyutlarına sahip olmanın bir yoludur . Bunu şu şekilde kullanırsın:

struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);

Bu eskiden standart değildi ve bir hack olarak kabul edildi (Aniket'in dediği gibi), ancak C99'da standartlaştırıldı . Şu an için standart biçim:

struct bts_action {
     u16 type;
     u16 size;
     u8 data[];
} __attribute__ ((packed)); /* Note: the __attribute__ is irrelevant here */

dataAlan için herhangi bir boyuttan bahsetmediğinizi unutmayın . Ayrıca bu özel değişkenin yalnızca yapının sonunda gelebileceğine dikkat edin.


C99'da bu konu 6.7.2.1.16'da açıklanmıştır (vurgu benim):

Özel bir durum olarak, birden fazla adlandırılmış üyeye sahip bir yapının son elemanı eksik bir dizi tipine sahip olabilir; buna esnek dizi üyesi denir. Çoğu durumda, esnek dizi üyesi göz ardı edilir. Özellikle, yapının boyutu, ihmalin ima ettiğinden daha fazla arka dolguya sahip olabilmesi dışında, esnek dizi üyesi çıkarılmış gibidir. Ancak, bir. (veya ->) operatörü, esnek bir dizi üyesine sahip bir yapı olan (bir gösterici) olan bir sol işlenene ve bu üyeye sağ işlenen adlarına sahiptir, bu üye en uzun diziyle (aynı öğe türüne sahip) değiştirilmiş gibi davranır. ) yapıyı erişilen nesneden daha büyük yapmaz; Dizinin ofseti, değiştirilen dizininkinden farklı olsa bile, esnek dizi üyesininki olarak kalacaktır. Bu dizinin hiç elemanı yoksa,

Veya başka bir deyişle, eğer varsa:

struct something
{
    /* other variables */
    char data[];
}

struct something *var = malloc(sizeof(*var) + extra);

var->dataİçindeki indeksler ile erişebilirsiniz [0, extra). sizeof(struct something)Bunun yalnızca diğer değişkenler için boyut muhasebesi vereceğini unutmayın , yani data0 boyutu verir .


Standardın gerçekte mallocböyle bir yapının örneklerini nasıl verdiğini not etmek de ilginç olabilir (6.7.2.1.17):

struct s { int n; double d[]; };

int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

Aynı konumdaki standardın bir başka ilginç notu da (vurgu benim):

malloc çağrısının başarılı olduğunu varsayarsak, p ile gösterilen nesne, çoğu amaç için p aşağıdaki gibi bildirilmiş gibi davranır:

struct { int n; double d[m]; } *p;

(bu denkliğin bozulduğu durumlar vardır; özellikle d üyesinin ofsetleri aynı olmayabilir ).


Açık olmak gerekirse, sorudaki orijinal kod C99'da (veya C11'de) hala standart değildir ve yine de bir hack olarak kabul edilecektir. C99 standardizasyonu, dizi sınırını içermemelidir.
MM

Nedir [0, extra)?
SS Anne


36

Bu aslında GCC ( C90 ) için bir hack'tir .

Aynı zamanda struct hack olarak da adlandırılır .

Yani bir dahaki sefere şunu derdim:

struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);

Şöyle demeye eşdeğer olacaktır:

struct bts_action{
    u16 type;
    u16 size;
    u8 data[100];
};

Ve bu tür yapı nesnelerinden istediğiniz sayıda oluşturabilirim.


7

Buradaki fikir, yapının sonunda değişken boyutlu bir diziye izin vermektir. Muhtemelen, bts_actionsabit boyutlu bir başlığı ( typeve sizealanları) ve değişken boyutlu dataüyesi olan bazı veri paketleridir . 0 uzunluklu bir dizi olarak bildirilerek, tıpkı diğer diziler gibi dizine alınabilir. Daha sonra bts_action1024 bayt databoyutunda bir yapı ayırırsınız , örneğin:

size_t size = 1024;
struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size);

Ayrıca bkz .: http://c2.com/cgi/wiki?StructHack


2
@Aniket: Bu fikrin nereden geldiğinden tam olarak emin değilim .
sheu

C ++ 'da evet, C' de gerekli değil.
amc

2
@sheu, bu, yazma tarzınızın mallocsizi birden çok kez tekrar etmesine neden olmasından kaynaklanıyor ve eğer herhangi bir actiondeğişiklik olursa, birden çok kez düzeltmeniz gerekiyor. Kendiniz için aşağıdaki iki karşılaştırın ve bileceksiniz: struct some_thing *variable = (struct some_thing *)malloc(10 * sizeof(struct some_thing));vs struct some_thing *variable = malloc(10 * sizeof(*variable));ikincisi değişiklik nedeniyle, daha kısa daha temiz ve net bir şekilde daha kolaydır.
Shahbaz

5

Kod geçerli değil C (buna bakın ). Linux çekirdeği, bariz nedenlerden ötürü, taşınabilirlikle en ufak bir ilgisi yoktur, bu nedenle çok sayıda standart dışı kod kullanır.

Yaptıkları şey, 0 dizi boyutuna sahip standart olmayan bir GCC genişletmesidir. Standart uyumlu bir program yazardı u8 data[];ve bu da aynı anlama gelirdi. Görünüşe göre Linux çekirdeğinin yazarları, eğer bir seçenek ortaya çıkarsa, işleri gereksiz yere karmaşık ve standart dışı yapmayı seviyorlar.

Daha eski C standartlarında, bir yapının boş bir diziyle sonlandırılması "struct hack" olarak biliniyordu. Diğerleri, amacını başka cevaplarda zaten açıkladı. C90 standardındaki struct hack, tanımsız bir davranıştır ve çökmelere neden olabilir, çünkü bir C derleyicisi yapının sonuna herhangi bir sayıda dolgu baytı eklemekte özgürdür. Bu tür dolgu baytları, yapının sonunda "hacklemeye" çalıştığınız verilerle çarpışabilir.

GCC erken dönemde, bunu tanımsız davranıştan iyi tanımlanmış davranışa dönüştürmek için standart olmayan bir uzantı yaptı. C99 standardı daha sonra bu kavramı uyarladı ve bu nedenle herhangi bir modern C programı bu özelliği risksiz olarak kullanabilir. C99 / C11'de esnek dizi üyesi olarak bilinir .


3
"Linux çekirdeğinin taşınabilirlikle ilgilenmediğinden" şüpheliyim. Belki de diğer derleyiciler için taşınabilirliği kastettiniz? Gcc'nin özellikleriyle oldukça iç içe olduğu doğrudur.
Shahbaz

3
Yine de, bu özel kod parçasının ana akım bir kod olmadığını ve muhtemelen yazarı ona fazla dikkat etmediği için dışarıda bırakıldığını düşünüyorum. Lisans, bazı texas enstrümanları sürücüleri hakkında olduğunu söylüyor, bu nedenle çekirdeğin çekirdek programcılarının buna dikkat etmesi pek olası değil. Çekirdek geliştiricilerinin eski kodu yeni standartlara veya yeni optimizasyonlara göre sürekli olarak güncellediğinden eminim. Her şeyin güncellendiğinden emin olmak için çok büyük!
Shahbaz

1
@Shahbaz "Bariz" kısımla, doğal olarak hiçbir anlam ifade etmeyen diğer işletim sistemlerine taşınabilirliği kastetmiştim. Ancak diğer derleyicilere taşınabilirlik de umurlarında değil gibi görünüyorlar, o kadar çok GCC uzantısı kullandılar ki, Linux muhtemelen başka bir derleyiciye taşınamayacak.
Lundin

3
@Shahbaz Texas Instruments olarak adlandırılan herhangi bir şey söz konusu olduğunda, TI'nin kendisi, çeşitli TI yongaları için uygulama notlarında şimdiye kadar görülen en işe yaramaz, berbat, saf C kodunu üretmekle ünlüdür. Kod TI kaynaklıysa, bundan yararlı bir şeyi yorumlama şansına ilişkin tüm bahisler iptal edilir.
Lundin

4
Linux ve gcc'nin birbirinden ayrılamaz olduğu doğrudur. Linux çekirdeğinin anlaşılması da oldukça zordur (çoğunlukla bir işletim sistemi zaten karmaşık olduğu için). Yine de, vurgulamak istediğim, üçüncü şahıslara ait kötü bir kodlama uygulaması nedeniyle "Linux çekirdeğinin yazarları görünüşe göre işleri gereksiz yere karmaşık ve standart dışı yapmayı seviyorlar, eğer böyle bir seçenek kendini ortaya koyuyorsa" demenin hoş olmamasıydı. .
Shahbaz 13

1

Sıfır uzunluklu dizinin başka bir kullanımı, derleme zamanı yapı ofset kontrolüne yardımcı olmak için bir yapı içinde adlandırılmış bir etiket gibidir.

Hem başlangıçta hem de sınırı geçtiği yerde önbellek çizgisi sınırına hizalandıklarından emin olmak istediğiniz bazı büyük yapı tanımlarına (birden çok önbellek satırına yayılır) sahip olduğunuzu varsayalım.

struct example_large_s
{
    u32 first; // align to CL
    u32 data;
    ....
    u64 *second;  // align to second CL after the first one
    ....
};

Kodda bunları aşağıdaki gibi GCC uzantılarını kullanarak bildirebilirsiniz:

__attribute__((aligned(CACHE_LINE_BYTES)))

Ancak yine de bunun çalışma zamanında uygulandığından emin olmak istiyorsunuz.

ASSERT (offsetof (example_large_s, first) == 0);
ASSERT (offsetof (example_large_s, second) == CACHE_LINE_BYTES);

Bu, tek bir yapı için işe yarayacaktır, ancak birçok yapıyı kapsamak zor olacaktır, her birinin hizalanacak farklı üye adı vardır. Büyük olasılıkla, her yapının ilk üyesinin adlarını bulmanız gereken aşağıdaki gibi bir kod alırsınız:

assert (offsetof (one_struct,     <name_of_first_member>) == 0);
assert (offsetof (one_struct,     <name_of_second_member>) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, <name_of_first_member>) == 0);
assert (offsetof (another_struct, <name_of_second_member>) == CACHE_LINE_BYTES);

Bu şekilde gitmek yerine, yapı içinde, tutarlı bir ada sahip adlandırılmış bir etiket gibi davranan ancak herhangi bir alan tüketmeyen sıfır uzunlukta bir dizi bildirebilirsiniz.

#define CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CACHE_LINE_BYTES)))
struct example_large_s
{
    CACHE_LINE_ALIGN_MARK (cacheline0);
    u32 first; // align to CL
    u32 data;
    ....
    CACHE_LINE_ALIGN_MARK (cacheline1);
    u64 *second;  // align to second CL after the first one
    ....
};

Daha sonra çalışma zamanı onaylama kodunun bakımı çok daha kolay olacaktır:

assert (offsetof (one_struct,     cacheline0) == 0);
assert (offsetof (one_struct,     cacheline1) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, cacheline0) == 0);
assert (offsetof (another_struct, cacheline1) == CACHE_LINE_BYTES);

İlginç fikir. 0 uzunluklu dizilere standart tarafından izin verilmediğine dikkat edin, bu nedenle bu derleyiciye özgü bir şeydir. Ayrıca, gcc'nin bir yapı tanımındaki 0 ​​uzunluklu dizilerin davranışına ilişkin tanımını alıntılamak, en azından bildirimden önce mi sonra mı doldurma ekleyebileceğini göstermek için iyi bir fikir olabilir.
Shahbaz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.