C 字节对齐,(windows unix), 指定bit 置1、清零的实现

#pragma pack(n) 与 __attribute(aligned(n))  原地址

在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
例如,下面的结构各成员空间分配情况:
struct test
{
     char x1;
     short x2;
     float x3;
     char x4;
};
结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对齐,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。
更改C编译器的缺省字节对齐方式
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。
一般地,可以通过下面的方法来改变缺省的对界条件:
     ·使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
     ·使用伪指令#pragma pack (),取消自定义字节对齐方式。
另外,还有如下的一种方式:
     ·__attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
     ·__attribute__((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
以上的n = 1, 2, 4, 8, 16... 第一种方式较为常见。

下面有一道在 CSDN论坛 上讨论火热的题:
#pragma pack(8)
struct s1
{
short a;
long b;
};

struct s2
{
char c;
s1 d;
long long e;
};
#pragma pack()

问:
1.sizeof(s2) = ?
2.s2的c后面空了几个字节接着是d?

结果如下:
1.sizeof(S2)结果为24。
成员对齐有一个重要的条件,即每个成员分别对齐。即每个成员按自己的方式对齐,也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐。其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐。并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。
S1中,成员a是2字节默认按2字节对齐,指定对齐参数为8,这两个值中取2,a按2字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;
S2中,c按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,一共使用了24个字节.
                a    b
S1的内存布局:11**,1111,
                c   S1.a S1.b     d
S2的内存布局:1***,11**,  1111,****   11111111

这里有三点很重要:
1.每个成员分别按自己的方式对齐,并能最小化长度
2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度
3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐

补充一下,对于数组,比如:
char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
如果写: typedef char Array3[3];
Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.

测试的编译器:
GCC 2.95 3.1 3.3 3.4 4.0
MS C/C++ 7.0 7.1 8.0 beta
Borland C/C++ 5.6 6.0
Intel C/C++ 7.0 8.0 8.1
DigitalMars C/C++ 8.4
OpenWatcom 1.3
Codeplay C/C++ 2.1.7

朋友帖了如下一段代码:
  #pragma pack(4)
  class TestB
  {
  public:
    int aa;
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestB);
  这里nSize结果为12,在预料之中。

  现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestC);
  按照正常的填充方式nSize的结果应该是8,为什么结果显示nSize为6呢?

事实上,很多人对#pragma pack的理解是错误的。
#pragma pack规定的对齐长度,实际使用的规则是:
结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
也就是说,当#pragma pack的值等于或超过所有数据成员长度的时候,这个值的大小将不产生任何效果。

而结构整体的对齐,则按照结构体中最大的数据成员和#pragma pack指定值 之间,较小的那个进行。

具体解释
#pragma pack(4)
  class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
这个类实际占据的内存空间是9字节,类之间的对齐,是按照类内部最大的成员的长度和#pragma pack规定的值之中较小的一个对齐的。

所以这个例子中,类之间对齐的长度是min(sizeof(int),4),也就是4。

9按照4字节圆整的结果是12,所以sizeof(TestB)是12。

如果
#pragma pack(2)
    class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(2),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(2),取2,按2字节对齐,所以放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
//可以看出,上面的位置完全没有变化,只是类之间改为按2字节对齐,9按2圆整的结果是10。
//所以 sizeof(TestB)是10。

最后看原贴:
现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;//第一个成员,放在[0]偏移的位置,
    short b;//第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。
    char c;//第三个,自身长为1,放在[4]的位置。
  };
//整个类的大小是5字节,按照min(sizeof(short),4)字节对齐,也就是2字节对齐,结果是6
//所以sizeof(TestC)是6。
感谢 Michael 提出疑问,在此补充:
当数据定义中出现__declspec( align() )时,指定类型的对齐长度还要用自身长度和这里指定的数值比较,然后取其中较大的。最终类/结构的对齐长度也需要和这个数值比较,然后取其中较大的。
可以这样理解,__declspec( align() ) 和 #pragma pack是一对兄弟,前者规定了对齐的最小值,后者规定了对齐的最大值,两者同时出现时,前者拥有更高的优先级。
__declspec( align() )的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:
在__declspec( align() )之前,数据按照#pragma pack规定的方式填充,如前所述。当遇到__declspec( align() )的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长度为max(数据自身长度,指定值) ),然后把被指定的数据类型从这个点开始填充,其后的数据类型从它的后面开始,仍然按照#pragma pack填充,直到遇到下一个__declspec( align() )。
当所有数据填充完毕,把结构的整体对齐数值和__declspec( align() )规定的值做比较,取其中较大的作为整个结构的对齐长度。
特别的,当__declspec( align() )指定的数值比对应类型长度小的时候,这个指定不起作用。
View Code

 

在windows下设置字节对齐大小的方式,目前我了解有三种:
在windows下设置字节对齐大小的方式,目前我了解有三种:


1. 在编译程序时候的编译选项  /Zp[n],如 cl /Zp4 表示对齐大小是4字节;

2. 预处理命令   #pragma pack( [ show ] | [ push | pop ] [, identifier ] , n  );

3. 微软特定命令  __declspec(align(#))。



下面我将分别介绍这三种,并分析不同方式的优缺点,先明确下字节对齐大小都是2的指数倍如1,2,4,8,16,32等。


方法一的用法最为简单,只需要在编译选项设置就行,该方式将作用于整个模块,因此是全局性的,不够灵活。

方法二的用法复杂点,但该方法可以在一个模块中不断的设置字节对齐大小,灵活性很强,针对不同的结构体采用不同的对齐大小,很适合这种方法,但用法较为复杂。

方法三的方法主要是作用于整个结构体上,是加强了字节对齐的限制,如将整个结构体对齐为64字节,不管里面元素总的大小,则结构体大小为大于总大小的最小的64倍数。


结构体大小的确定以及每个元素位置的确定:

对齐大小为 align(方法一和方法二设置的值,在vs2013中默认为8字节)、结构体中元素字节最大为max、结构体对齐align_struct(由方法三进行设置)

       假设内存是一个一个篮子用于存放结构体的每个变量, 而篮子肯定有大小   = min(align, max)大小为对齐大小和元素字节最大的 较小者。

下面就是将元素放入篮子的问题,有个原则是:

"The alignment of a member will be on a boundary that is either a multiple ofn or a multiple of the size of the member, whichever is smaller."

每个元素放置的位置 要么是 对齐大小的整数倍,要么是 该元素大小的整数倍,比较这两个值,哪个小就放在那。

若没有方法三设置,则最后结构体的大小就为篮子 大小的倍数,若有方法三的设置最后在考虑结构体整体的字节对齐。下面举个例子:


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
typedef struct A  
{  
    char ca;  
    int ib;  
    short sc;  
    int  id;  
    double de;  
}A;  

若对齐大小align=4, 则 max = 8 = sizeof(double), 篮子大小为 min(4,8) = 4.
char ca 放在第一个篮子中, 偏移为0

int ib 要放在的位置必须为4的倍数,所以放在第二个篮子中,偏移为4

short sc 要放在的位置为2的倍数, 所以放在第三个篮子中,偏移为8

       int id要放在位置为4的倍数, 第三个篮子没有这样的位置,所以放在第四个篮子中,偏移为12

      double要放在位置为4的倍数,放在第五个篮子和第六个篮子中,偏移为16,

可以看出总共用了6个篮子,总大小sizeof(A) = 24.


若添加方法三在结构体上 如下

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
typedef struct __declspec( align( 32) ) A  
{  
    char ca;  
    int ib;  
    short sc;  
    int  id;  
    double de;  
}A;  
 修饰结构体的对齐大小为32,则sizeof(A) = 32 ,但每个元素在该结构体中偏移和上面都一样,只是在结构体的末尾添加字节用于对齐。

      现在只是简单介绍了三种方法和结构体大小的确定 ,将在后面具体介绍方法二和方法三的用法。

 

 

#pragma pack 预处理命令

pack 为 struct, union 和 class 等的成员对齐指定字节边界. 与编译选项的 /Zp 开关不同, 它不针对整个项目, 而仅针对模块, 比如一个编译单元.
 
1. #pragma pack(show)
    以警告信息的形式显示当前字节对齐的值.
2. #pragma pack(n)
    将当前字节对齐值设为 n .
3. #pragma pack()
    将当前字节对齐值设为默认值(通常是8) .
4. #pragma pack(push)
    将当前字节对齐值压入编译栈栈顶.
5. #pragma pack(pop)
    将编译栈栈顶的字节对齐值弹出并设为当前值.
6. #pragma pack(push, n)
    先将当前字节对齐值压入编译栈栈顶, 然后再将 n 设为当前值.
7. #pragma pack(pop, n)
    将编译栈栈顶的字节对齐值弹出, 然后丢弃, 再将 n 设为当前值.
8. #pragma pack(push, identifier)
    将当前字节对齐值压入编译栈栈顶, 然后将栈中保存该值的位置标识为 identifier .
9. #pragma pack(pop, identifier)
    将编译栈栈中标识为 identifier 位置的值弹出, 并将其设为当前值. 注意, 如果栈中所标识的位置之上还有值, 那会先被弹出并丢弃.
10. #pragma pack(push, identifier, n)
    将当前字节对齐值压入编译栈栈顶, 然后将栈中保存该值的位置标识为 identifier, 再将 n 设为当前值.
11. #pragma pack(pop, identifier, n)
    将编译栈栈中标识为 identifier 位置的值弹出, 然后丢弃, 再将 n 设为当前值. 注意, 如果栈中所标识的位置之上还有值, 那会先被弹出并丢弃.

 

 

 

C++中的字节对齐分析

 

 

 

 

/*
  指定位  置位、清零、读取
  http://www.cnblogs.com/scotth/p/4254687.html 
  by:scott-h
*/
#ifdef WIN32
#else //linux #include <unistd.h> #include <errno.h> #endif #include <string.h> #include <stdlib.h> #include <stdio.h> #ifdef WIN32 #else //linux struct tagUID { char uid[8];//char uid[16]; }__attribute__((aligned(4)));//GCC 对齐 #endif unsigned int setbit(unsigned int val,const unsigned int nIndex) { return val |= 1 << nIndex; } unsigned int cleanbit(unsigned int val,const unsigned int nIndex) { return val &= ~(1 << nIndex); } unsigned int readbit(unsigned int val,const unsigned int nIndex) { //return val &= 1 << nIndex; printf(" val=0x%08x , ret=0x%08x nIndex=%d.",val, val & (1<<nIndex), nIndex); //bool bret = val & (1<<nIndex)?1:0; return (val & (1<<nIndex))?1:0; } int _tmain(int argc, _TCHAR* argv[]) { #ifdef WIN32 #else unsigned char readBuffer[16] = {0};//unsigned char readBuffer[32] = {0}; struct tagUID strTemp = {254,253,252,251,250,249,248,247}; sprintf(readBuffer,"%02x%02x%02x%02x%02x%02x%02x%02x", strTemp.uid[0] & ~0xFF,strTemp.uid[1] & 0xFF,strTemp.uid[2] & 0xFF,strTemp.uid[3] & 0xFF, strTemp.uid[4] & 0xFF,strTemp.uid[5] & 0xFF,strTemp.uid[6] & 0xFF,strTemp.uid[7] & 0xFF); #endif // 只取低4 + 4bit(低2BYTE),的值 //method 1 //strTemp.uid[0] & 0xFF (为什么要& 0xFF 平台通用性,x86,x64都OK) // 只取低 4 + 4bit之外的值, (低 4 + 4bit 置为0) //Method 1 //strTemp.uid[0] & ~0xFF 先按位NOT,再按位AND //method 2 位移 //strTemp.uid[0] >> 8; //strTemp.uid[0] << 8; //printf("\n result1 =%s \n",readBuffer); unsigned char uc_a = 0x00;// 0000 0000 8位 1byte unsigned char uc_b = 0xFF;// 1111 1111 printf("\n res = 0x%02x \n",setbit(uc_a, 5)); printf("\n res = 0x%02x \n",cleanbit(uc_b, 4)); unsigned int ui_a = 0x00000000;// 0000 0000,0000 0000,0000 0000,0000 0000, windows 下 unsigned int 32位 4byte unsigned int ui_b = 0xFFFFFFFF;// 1111 1111,1111 1111,1111 1111,1111 1111, printf("\n res = 0x%08x \n",setbit(ui_a, 31)); printf("\n res = 0x%08x \n",cleanbit(ui_b, 0)); unsigned int ui_c = 0x80000000; printf("\n res = %d \n",readbit(ui_c, 31)); return 0; }

 

这里还有另外一个哥们的实现,感觉实现的功能多了点,可以参考下 http://en.sharejs.com/cpp/12050

/*
   Copyright 2011 Shao-Chuan Wang <shaochuan.wang AT gmail.com>
 
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
 
    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.
 
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
 
typedef struct {
  int size;
  unsigned int *b;
} packed_bits;
 
#define INT_NBITS (sizeof(unsigned int)*8)
#define INT_MAX_BIT_MASK (1 << (INT_NBITS-1))
 
 
packed_bits *new_bits(unsigned int n_int)
{
  packed_bits *p;
  unsigned int *b_array = calloc(n_int, sizeof(unsigned int));
  if (!b_array)
    return NULL;
  p = malloc(sizeof(packed_bits));
  if (!p) {
    free(b_array);
    return NULL;
  }
  p->size = n_int;
  p->b = b_array;
  return p;
}
 
int left_shift(packed_bits *p)
{
  int i;
  unsigned int *b;
  if (!p)
    return -1;
  b = p->b;
  for (i = p->size-1;i >= 0;i--) {
    b[i] = b[i] << 1;
    if (i-1 >=0 && b[i-1] & INT_MAX_BIT_MASK)
      b[i]++;
  }
  return 0;
}
 
int right_shift(packed_bits *p)
{
  int i;
  unsigned int *b;
  if (!p)
    return -1;
  b = p->b;
  for (i = 0;i < p->size;i++) {
    b[i] = b[i] >> 1;
    if (i+1 < p->size && (b[i+1] & 1))
      b[i] |= INT_MAX_BIT_MASK;
  }
  return 0; 
}
 
unsigned int read_bit(packed_bits *p, unsigned int n)
{
  unsigned int offset = n % INT_NBITS;
  unsigned int idx = n / INT_NBITS;
  unsigned int *b;
  if (!p)
    return -1;
  b = p->b;
  return (b[idx] & (1 << offset)) != 0;
}
 
/* n starts from 0 */
int set_bit(packed_bits *p, unsigned int n)
{
  unsigned int offset = n % INT_NBITS;
  unsigned int idx = n / INT_NBITS;
  unsigned int *b;
  if (!p)
    return -1;
  b = p->b;
  b[idx] |= (1<<offset);
  return 0;
}
 
int clear_bit(packed_bits *p, unsigned int n)
{
  unsigned int offset = n % INT_NBITS;
  unsigned int idx = n / INT_NBITS;
  unsigned int *b;
  if (!p)
    return -1;
  b = p->b;
  b[idx] &= ~(1<<offset);
 
  return 0;
}
 
void print_bits(packed_bits *p)
{
  int j;
  unsigned int *b;
  if (!p)
    return;
  b = p->b;
  for (j = (INT_NBITS * p->size) - 1; j >= 0; j--) {
    printf("%d", read_bit(p, j));
    if (j % INT_NBITS==0)
      printf("\n");
  }
  printf("\n");
  return;
}
 
int main(int argc, char *argv[])
{
  int n_int = 4;
  packed_bits *p = new_bits(n_int);
  if (!p) {
    fprintf(stderr, "Out of memory!\n");
    return EXIT_FAILURE;
  }
   
  set_bit(p, 1);
  set_bit(p, 127);
  print_bits(p);
   
  return 0;
}
View Code

 

 

 

https://msdn.microsoft.com/en-us/library/2e70t5y1.aspx

 

 gcc中文手册

gcc_attribute 机制

 

 


Windows

#pragma pack()

https://msdn.microsoft.com/en-us/library/2e70t5y1.aspx




使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
使用伪指令#pragma pack (),取消自定义字节对齐方式。

另外,还有如下的一种方式:
__attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
__attribute__((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

以上的
n = 1, 2, 4, 8, 16...
第一种方式较为常见。

 

 

posted @ 2015-04-07 17:27  scott_h  阅读(856)  评论(0编辑  收藏  举报