C++ sizeof 及 涉及的内存对齐

关于sizeof的问题,本人对这个问题也一直没有得到很好的解决,这里对平日收集总结作了些许整理,以便以后翻阅.

一、首先看看sizeof和strlen在MSDN上的定义:

首先看一MSDN上如何对sizeof进行定义的:

sizeof Operator

sizeof expression

The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type
(including aggregate types). This keyword returns a value of type size_t.

The expression is either an identifier or a type-cast expression (a type specifier enclosed in
parentheses).

When applied to a structure type or variable, sizeof returns the actual size, which may include
padding bytes inserted for alignment. When applied to a statically dimensioned array, sizeof
returns the size of the entire array. The sizeof operator cannot return the size of dynamically
allocated arrays or external arrays.
然后再看一下对strlen是如何定义的:

strlen

Get the length of a string.

Routine Required Header:
strlen <string.h>

size_t strlen( const char *string );
Parameter
string:Null-terminated string
Libraries
All versions of the C run-time libraries.

Return Value
Each of these functions returns the number of characters in string, excluding the terminal
NULL. No return value is reserved to indicate an error.

Remarks
Each of these functions returns the number of characters in string, not including the
terminating null character. wcslen is a wide-character version of strlen; the argument of
wcslen is a wide-character string. wcslen and strlen behave identically otherwise.

二、由几个例子说开去。

第一个例子:
char* ss = "0123456789";
sizeof(ss) 结果 4 ===》ss是指向字符串常量的字符指针
sizeof(*ss) 结果 1 ===》*ss是第一个字符

char ss[] = "0123456789";
sizeof(ss) 结果 11 ===》ss是数组,计算到/0位置,因此是10+1
sizeof(*ss) 结果 1 ===》*ss是第一个字符

char ss[100] = "0123456789";
sizeof(ss) 结果是100 ===》ss表示在内存中的大小 100×1
strlen(ss) 结果是10 ===》strlen是个函数内部实现是用一个循环计算到/0为止之前

int ss[100] = "0123456789";
sizeof(ss) 结果 400 ===》ss表示再内存中的大小 100×4
strlen(ss) 错误 ===》strlen的参数只能是char* 且必须是以''/0''结尾的

char q[]="abc";
char p[]="a/n";
sizeof(q),sizeof(p),strlen(q),strlen(p);
结果是 4 3 3 2     
第二个例子:
class X
{
int i;
int j;
char k;
};
X x;
cout<<sizeof(X)<<endl; 结果 12 ===》内存补齐
cout<<sizeof(x)<<endl; 结果 12 同上

第三个例子:
char szPath[MAX_PATH]

  如果在函数内这样定义,那么sizeof(szPath)将会是MAX_PATH,但是将szPath作为虚参声明时(void fun(char szPath[MAX_PATH])),sizeof(szPath)却会是4(指针大小)


三、sizeof深入理解。

1.sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
2.sizeof是算符,strlen是函数。
3.sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''/0''结尾的。sizeof还可以用函数做参数,比如:
short f();
printf("%d/n", sizeof(f()));

输出的结果是sizeof(short),即2。
4.数组做sizeof的参数不退化,传递给strlen就退化为指针了。
5.大部分编译程序 在编译的时候就把sizeof计算过了 是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
char str[20]="0123456789";
int a=strlen(str); //a=10;
int b=sizeof(str); //而b=20;

6.strlen的结果要在运行的时候才能计算出来,时用来计算字符串的长度,不是类型占内存的大小。
7.sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。
8.当适用了于一个结构类型时或变量, sizeof 返回实际的大小, 当适用一静态地空间数组, sizeof 归还全部数组的尺 寸。 sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸
9.数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,如:
fun(char [8])
fun(char [])

都等价于 fun(char *) 在C++里传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小如果想在函数内知道数组的大小, 需要这样做:进入函数后用memcpy拷贝出来,长度由另一个形参传进去
fun(unsiged char *p1, int len)
{
  unsigned char* buf = new unsigned char[len+1]
  memcpy(buf, p1, len);
}

有关内容见: C++ PRIMER?
10.计算结构变量的大小就必须讨论数据对齐问题。为了CPU存取的速度最快(这同CPU取数操作有关,详细的介绍可以参考一些计算机原理方面的书),C++在处理数据时经常把结构变量中的成员的大小按照4或8的倍数计算,这就叫数据对

齐(data alignment)。这样做可能会浪费一些内存,但理论上速度快了。当然这样的设置会在读写一些别的应用程序生成的数据文件或交换数据时带来不便。MS VC++中的对齐设定,有时候sizeof得到的与实际不等。一般在VC++中加上

#pragma pack(n)的设定即可.或者如果要按字节存储,而不进行数据对齐,可以在Options对话框中修改Advanced compiler页中的Data alignment为按字节对齐。
11.sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。如sizeof(max)若此时变量max定义为int max(),sizeof

(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式

四 sizeof使用场合。
 
1.sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如: 
  void *malloc(size_t size), 
  size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。

2.用它可以看看一类型的对象在内存中所占的单元字节。
void * memset(void * s,int c,sizeof(s))

3.在动态分配一对象时,可以让系统知道要分配多少内存。
4.便于一些类型的扩充,在windows中就有很多结构内型就有一个专用的字段是用来放该类型的字节大小。
5.由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。
6.如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。

 

五.sizeof 涉及的内存对齐

简单的规则是,在vc编译器下:内存对齐为最长类型的整数倍,不足补齐。
LINUX下的GCC对结构体对齐默认情况下用以下的标准:  
  1.保证结构体变量本身的地址一定是4的整倍数  
  2.保证结体中任何变量所处地址按其自身的自然对齐方式。
 

struct   st  
  {  
  int   i[4];             //   4,     0   %   4     =   0,     4   *   5 ----   这里是4个整型连续分布。这里可能涉及整个结构的平移  
  double   b;     //   8,   16   %   8   =   0,     8  
  int   a;                   //   4,   24   %   4   =   0,     4  
  };                             //   max(4,   8,   4)   =   8,   (16   +   8   +   4)   %   8   !=   0   因此整个结构需要补齐,在后边补齐,   32  
因此 st x;
     sizeof(x) = 32 
   
  struct   st  
  {  
  int   i[5];             //   4,   offset   +   4   ×   5  
  double   b;   //     8,   20   %   8   !=   0,   因此向前补   4   个字节从   24开始分配   b   的内存,   8  
  int   a;                 //     4,   (20   +   4   +   8)   %   4   =   0,   4  
  };                           //     max(4,   8,   4)   =   8,   (20   +   4   +   8   +   4)   %   8   !=   0,   整个结构需要补齐,在后边补齐,   40  

因此 st x;
     sizeof(x) = 40

比如struct   gg  
  {  
  char   a;  
  }  
  这个结构体的变量确实实际占用的4个字节,因为cpu按字寻址,  
  但是sizeof(struct   gg)=1;

但我们这里用sizeof(...) = 1并不能说明问题,更好的方法是在需要在定义一个gg的变量后,再在其后定义另一个变量,将这两个变量的地址相减才能求出实际结果,而且你的测试一定是在VC中,GCC中是4个字节的  
   
  不能用这种方式来求结构体占用的空间,这是不准确的,不过在另一种程度上可以说得过去,因为结构体地址是被4整除,而结构体的第一个成员所在地址与结构体本身所占地址之间没有空洞存在的,而其成员又只占一个字节,所以VC编

译器的sizeof语句就把它设成了一个字节,这跟编译器密切相关,你换成GCC之后就不一样了,无论这个表达示的结果如何,其内存占用空间的布局是可以预测的,只要知道了这个道理,就可以举一反三了。
  之所以在VC下的sizeof设成这种规则,其目的是让人更好理解,屏蔽了细节,让人知其然不知其所以然,细想一下:什么时候会用sizeof,无非就是填充数据的时候,如果你需要动态地确定该填多少字节,比较好的方法就是先用sizeof

这个表达示求出来,在  
  struct   gg  
  {  
  char   a;  
  }  
  中,写代码的人一看就知道只有一个字节被占用,所以理所当然就只填充一个字节进去,余下的工作就交给编译器去做,因为编译器会自动填充余下的三个空洞,而但是sizeof(struct   gg)=1让人看起来更自然,因为其内容就只有

一个字节啊,当然结果就应该是1个字节了啊,无论你将sizeof表达示的值置成1或者4,对程序结果没有任何影响,用下面的例子来说明:  
  struct   gg   test;  
  memset(&test,0,sizeof(struct   gg));  
  如果sizeof(struct   gg)结果是1,那么内存中接下来的三个字节就是编译器填充的固定字符,这个字符在GCC下就是0xc,   也就是说内存中第一个字节是0,接下来的三个字节是0xc,如果sizeof(struct   gg)结果是4,那么内存中的

四个字节都是0,说到这里,好像结果造成了明显的不同之处,但实际没有什么影响,接下来,如果调用下面一条语句:  
  test.a='a';  
  那么编译器会帮你做一个工作,将其反汇编看一下就明白了,就是将四个字节全部取出,但会忽略掉三个字节,也就是让那三个字节形同虚设,而只取那个a所占8位的数据,并修改其数据,其余24位不变,最后再存回内存,所以无论你

的sizeof(struct   gg)是1或是4,对你的代码是透明的!!!!!而微软的东东一向就是以易用为原则,为了让程序员更好理解,所以就让sizeof(struct   gg)等于1,而不是4,随之而来的就会给想要了解其细节的人设了一道障碍,而

GCC刚好相反,处处在显示其实现细节,而不是隐藏细节  
   
  
  我们可以写下下面的测试代码,观测其执行结果,再回头来思考:  
  #include   <stdio.h>  
  struct   gg  
  {  
  char   a;  
  }  
   
  stuct   gg   test;  
  char   test_str_temp1[4];  
  char   test_str_temp2[4];  
   
  int   main(int   argc,char   **argv)  
  {  
          memcpy(test_str_temp1,&test,4);  
        test.a   =   'a';  
        memcpy(test_str_temp2,&test,4);  
          printf("*******0x%x   0x%x   0x%x   0x%x           *******/n",test_str_temp1[0],test_str_temp1[1],test_str_temp1[2],test_str_temp1[3]);  
          printf("*******0x%x   0x%x   0x%x   0x%x   *******/n",test_str_temp2[0],test_str_temp2[1],test_str_temp2[2],test_str_temp2[3]);  
          printf("------0x%x---------/n",test.a);  
          return   0;  
  }


用下面的程序更好说明这个问题的:  
  #include   <stdio.h>  
   
  char   test_str_temp1[4];  
  char   test_str_temp2[4];  
  char   test_str_temp3[4];  
  int   main(int   argc,char   **argv)  
  {  
  struct   gg  
  {  
  char   a;  
  };  
   
  struct   gg   test;  
  int   empty;  
  empty   =   1;  
  memcpy(test_str_temp1,&test,4);  
  test.a   =   'a';  
  memcpy(test_str_temp2,&test,4);  
  memcpy(test_str_temp3,&empty,4);  
  printf("*******0x%x   %d   %d   %d   *******/n",test_str_temp1[0],(int)test_str_temp1[1],(int)test_str_temp1[2],(int)test_str_temp1[3]);  
  printf("*******0x%x   %d   %d   %d   *******/n",test_str_temp2[0],(int)test_str_temp2[1],(int)test_str_temp2[2],(int)test_str_temp2[3]);  
  printf("------0x%x---------/n",test.a);  
  printf("*******0x%x   %d   %d   %d   *******/n",test_str_temp3[0],(int)test_str_temp3[1],(int)test_str_temp3[2],(int)test_str_temp3[3]);  
   
  return   0;  
  }  

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值