C / C ++: Zorla Bit Alan Sırası ve Hizalama


87

Bir yapı içindeki bit alanlarının sırasının platforma özgü olduğunu okudum. Derleyiciye özgü farklı paketleme seçenekleri kullanırsam, bu garanti verileri yazıldıkları sırada doğru sırada depolanır mı? Örneğin:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

GCC derleyicili bir Intel işlemcide alanlar, gösterildiği gibi bellekte düzenlenmiştir. Message.versiontampondaki ilk 3 bitti ve Message.typebunu izledi. Çeşitli derleyiciler için eşdeğer yapı paketleme seçenekleri bulursam, bu çapraz platform olacak mı?


17
Bir tampon, bit değil, bir bayt kümesi olduğundan, "tampondaki ilk 3 bit" kesin bir kavram değildir. İlk baytın en düşük 3 bitinin ilk 3 biti mi yoksa en yüksek 3 biti mi olduğunu düşünürdünüz?
caf

2
Ağ üzerinden aktarılırken, "tampondaki ilk 3 bit" çok iyi tanımlanmış olarak ortaya çıkıyor .
Joshua

2
@Joshua IIRC, Ethernet ilk önce her baytın en önemsiz bitini iletir (bu nedenle yayın biti olduğu yerdir).
tc.

"Taşınabilir" ve "çapraz platform" derken neyi kastediyorsunuz? Yürütülebilir dosya, hedef işletim sisteminden bağımsız olarak siparişe doğru şekilde erişecek mi - yoksa kod araç zincirinden bağımsız olarak derlenecek mi?
Garet Claborn

Yanıtlar:


103

Hayır, tamamen taşınabilir olmayacak. Yapılar için paketleme seçenekleri uzantılardır ve kendileri tamamen taşınabilir değildir. Buna ek olarak, C99 §6.7.2.1, paragraf 10 şöyle der: "Bir birim içindeki bit alanlarının tahsisi sırası (yüksek sıradan düşük sıraya veya düşük sıradan yüksek sıraya) uygulama tanımlıdır."

Örneğin tek bir derleyici bile, hedef platformun sonluluğuna bağlı olarak bit alanını farklı şekilde yerleştirebilir.


Evet, örneğin GCC, özellikle bit alanlarının uygulamaya değil ABI'ye göre düzenlendiğini belirtiyor. Bu nedenle, tek bir derleyicide kalmak, sıralamayı garanti etmek için yeterli değildir. Mimarinin de kontrol edilmesi gerekiyor. Taşınabilirlik için biraz kabus, gerçekten.
underscore_d

10
C standardı neden bit alanları için bir sıra garanti etmedi?
Aaron Campbell

8
Bayt sınırlarını aşabilecek bitlerin sırasından çok daha az, baytlar içinde bitlerin "sırasını" tutarlı ve taşınabilir bir şekilde tanımlamak zordur. Kararlaştırdığınız herhangi bir tanım, önemli miktarda mevcut uygulama ile eşleşmeyecektir.
Stephen Canon

2
uygulama tanımlı, platforma özel optimizasyona izin verir. Bazı platformlarda, bit alanları arasındaki doldurma erişimi iyileştirebilir, 32 bitlik bir int içinde dört yedi bitlik alan hayal edin: bunları her 8. bitte hizalamak, bayt okumaları olan platformlar için önemli bir gelişmedir.
peterchen


45

Üzgünüz, bit alanları derleyiciden derleyiciye değişir.

GCC ile, büyük endian makineleri önce büyük uçları yerleştirir ve küçük endian makineleri önce küçük uçları yerleştirir.

K&R, "Yapıların bitişik [bit-] alanı üyeleri, uygulamaya bağlı bir yönde uygulamaya bağlı depolama birimlerine paketlenir. Başka bir alanı takip eden bir alan uymadığında ... birimler arasında bölünebilir veya birim olabilir dolgulu. Genişliği 0 olan adlandırılmamış bir alan bu dolguyu zorlar ... "

Bu nedenle, makineden bağımsız ikili düzene ihtiyacınız varsa, bunu kendiniz yapmalısınız.

Bu son ifade, doldurma nedeniyle bit olmayan alanlar için de geçerlidir - ancak GCC için zaten keşfetmiş olduğunuzu gördüğüm gibi, tüm derleyiciler bir yapının bayt paketlemesini zorlamanın bir yolunu bulmuş gibi görünüyor.


Ön standardizasyon olduğu ve (varsayıyorum?) Muhtemelen birçok alanda yerini aldığı göz önüne alındığında, K&R gerçekten yararlı bir referans olarak kabul ediliyor mu?
underscore_d

1
K & R'm ANSI sonrası.
Joshua

1
Şimdi bu utanç verici: ANSI sonrası bir revizyon yayınladıklarını bilmiyordum. Benim hatam!
underscore_d

35

Bitfield'lardan kaçınılmalıdır - aynı platform için bile derleyiciler arasında çok taşınabilir değildirler. C99 standardı 6.7.2.1/10 - "Yapı ve birleşim belirleyicileri" (C90 standardında benzer ifadeler vardır):

Bir uygulama, bir bit alanını tutmaya yetecek büyüklükte herhangi bir adreslenebilir depolama birimini tahsis edebilir. Yeterli alan kalırsa, bir yapıdaki başka bir bit alanını hemen takip eden bir bit alanı, aynı birimin bitişik bitlerine paketlenecektir. Yetersiz alan kalırsa, uymayan bir bit alanının sonraki birime mi konulacağı veya bitişik birimlerle örtüşüp örtüşmediği uygulama tanımlıdır. Bir birimdeki bit alanlarının tahsis sırası (yüksek sıradan düşük sıraya veya düşük sıradan yüksek sıraya) uygulama tanımlıdır. Adreslenebilir depolama biriminin hizalanması belirtilmedi.

Bir bit alanının bir int sınırını 'kapsayıp kapsamayacağını' garanti edemezsiniz ve bir bit alanının int'in alt ucunda mı yoksa int'in üst ucunda mı başlayacağını belirleyemezsiniz (bu, işlemcinin big-endian veya little-endian).

Bit maskelerini tercih edin. Bitleri ayarlamak, temizlemek ve test etmek için satır içi (hatta makrolar) kullanın.


2
Bit alanlarının sırası, derleme zamanında belirlenebilir.
Greg A. Woods

9
Ayrıca, program dışında herhangi bir harici gösterime sahip olmayan (yani diskte veya yazmaçlarda veya diğer programlar tarafından erişilen bellekte, vb.) Bit bayraklarıyla uğraşırken bit alanları oldukça tercih edilir.
Greg A. Woods

1
@ GregA.Woods: Eğer durum gerçekten böyleyse, lütfen nasıl yapılacağını açıklayan bir cevap verin. Google'da arama yaparken yorumunuzdan başka bir şey bulamadım ...
mozzbozz

1
@ GregA.Woods: Üzgünüm, hangi yoruma atıfta bulunduğumu yazmalıydım. Demek istediğim: "Bit alanlarının sırası derleme zamanında belirlenebilir." Diyorsunuz. Onun hakkında ve nasıl yapılacağı hakkında hiçbir şey yapamam.
mozzbozz

2
@mozzbozz göz at planix.com/~woods/projects/wsg2000.c ve tanımlar aramak ve kullanımı _BIT_FIELDS_LTOHve_BIT_FIELDS_HTOL
Greg A. Woods

11

endianness bit emirlerinden değil bayt sıralarından bahsediyor. Günümüzde bit siparişlerinin sabitlendiğinden% 99 emin. Bununla birlikte, bit alanlarını kullanırken, endianness hesaba katılmalıdır. Aşağıdaki örneğe bakın.

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a

6
A ve b'nin çıktısı, bitimliliğin hala bit sıralarından VE bayt sıralarından bahsettiğini gösterir.
Windows programcısı

bit sıralaması ve bayt sıralaması problematiği ile harika bir örnek
Jonathan

1
Kodu gerçekten derlediniz ve çalıştırdınız mı? "A" ve "b" değerleri bana mantıklı gelmiyor: temelde derleyicinin bitkinlik nedeniyle bitleri bir bayt içinde değiştireceğini söylüyorsunuz. "D" durumunda, endianlar char dizilerindeki bayt sırasını etkilememelidir (char'ın 1 bayt uzunluğunda olduğu varsayılırsa); derleyici bunu yapsaydı, işaretçiler kullanarak bir diziyi yineleyemezdik. Öte yandan, 16 bitlik iki tamsayı dizisi kullandıysanız, örneğin: uint16 data [] = {0x1234,0x5678}; küçük endian sistemlerde d kesinlikle 0x7856 olacaktır.
Krauss

6

Muhtemelen çoğu zaman, ama çiftlikte bahse girmeyin, çünkü yanılıyorsanız, büyük kaybedersiniz.

Gerçekten, gerçekten aynı ikili bilgiye ihtiyacınız varsa, bit maskeleri ile bit alanları oluşturmanız gerekir - örneğin, Mesaj için işaretsiz bir kısa (16 bit) kullanırsınız ve ardından en üstteki üç biti temsil etmek için versionMask = 0xE000 gibi şeyler yaparsınız.

Yapılar içinde hizalamayla ilgili benzer bir sorun var. Örneğin, Sparc, PowerPC ve 680x0 CPU'ların tümü büyüktür ve Sparc ve PowerPC derleyicileri için ortak varsayılan, yapı üyelerini 4 baytlık sınırlarda hizalamaktır. Ancak, 680x0 için kullandığım bir derleyici yalnızca 2 baytlık sınırlarla hizalandı - ve hizalamayı değiştirme seçeneği yoktu!

Dolayısıyla, bazı yapılar için Sparc ve PowerPC'deki boyutlar aynıdır, ancak 680x0'da daha küçüktür ve bazı üyeler yapı içinde farklı bellek uzaklıklarındadır.

Bu, üzerinde çalıştığım bir projeyle ilgili bir sorundu, çünkü Sparc üzerinde çalışan bir sunucu işlemi bir istemciyi sorgulayıp onun büyük bir endian olduğunu anlar ve ağdaki ikili yapıları fışkırtarak istemcinin başa çıkabileceğini varsayardı. Ve bu, PowerPC istemcilerinde iyi çalıştı ve 680x0 istemcilerde büyük ölçüde çöktü. Kodu ben yazmadım ve sorunu bulmam epey zaman aldı. Ama bir kez yaptıktan sonra düzeltmek kolaydı.


1

Çok yararlı yorumunuz için teşekkürler @BenVoigt

Hayır, hafızadan tasarruf etmek için yaratıldılar.

Linux kaynağı , harici bir yapıyla eşleştirmek için bir bit alanı kullanır: /usr/include/linux/ip.h , bir IP datagramının ilk baytı için bu koda sahiptir

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

Ancak yorumunuzun ışığında, bunu multi-byte bit alanı frag_off için çalıştırmaya çalışmaktan vazgeçiyorum .


-9

Elbette en iyi cevap, bit alanlarını akış olarak okuyan / yazan bir sınıf kullanmaktır. C bit alan yapısının kullanılması garanti edilmez. Bunu gerçek dünya kodlamasında kullanmanın amatörce / tembel / aptalca kabul edildiğinden bahsetmiyorum bile.


5
C de modellemek için oluşturulmuş donanım kayıtlarını temsil etmek için çok temiz bir yol sağladığından bit alanlarını kullanmanın aptalca olduğunu söylemek yanlış olur.
trondd

13
@trondd: Hayır, hafızadan tasarruf etmek için oluşturuldu. Bitfields, bellek eşlemeli donanım kayıtları, ağ protokolleri veya dosya formatları gibi dış veri yapılarıyla eşleşmeyi amaçlamaz. Dış veri yapılarıyla eşleşmeleri amaçlansaydı, paketleme sırası standartlaştırılmış olurdu.
Ben Voigt

2
Bit kullanmak hafızadan tasarruf sağlar. Bit alanlarının kullanılması okunabilirliği artırır. Daha az bellek kullanmak daha hızlıdır. Bitlerin kullanılması daha karmaşık atomik işlemlere izin verir. Gerçek dünyadaki uygulamalarda, performansa ve karmaşık atomik işlemlere ihtiyaç vardır. Bu cevap bizim için işe yaramaz.
johnnycrash

@BenVoigt muhtemelen doğru, ancak bir programcı kendi derleyici sipariş / ABI ihtiyaç duyduklarını maçlar ve buna göre hızlı taşınabilirliği feda onaylamak için istekli olup olmadığını - o zaman onlar kesinlikle olabilir rolü yerine getirmek. 9 * 'a gelince, hangi yetkili "gerçek dünya kodlayıcıları" bit alanlarının tüm kullanımlarının "amatörce / tembel / aptalca" olduğunu düşünüyor ve bunu nerede ifade ettiler?
underscore_d

2
Daha az bellek kullanmak her zaman daha hızlı değildir; daha fazla bellek kullanmak ve okuma sonrası işlemleri azaltmak genellikle daha verimlidir ve işlemci / işlemci modu bunu daha da doğru hale getirebilir.
Dave Newton
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.