sizeof() 与strlen()

来源:互联网 发布:手机淘宝直播入口在哪 编辑:程序博客网 时间:2024/04/28 22:26

    sizeof 是C/C++的一种运算符,用来返回数据类型占用内存的字节数。返回类型是 size_t 其实就是 typedef unsigned int size_t;

     它有三种形式:

     1) sizeof( object ); // sizeof( 对象 );

 2) sizeof( type_name ); // sizeof( 类型 );

 3) sizeof object; // sizeof 对象;

   比如 int a;

     sizeof(a) ,sizeof(int )  ,sizeof  a 都是合法的。一般在程序中使用sizeof() 较多。 由于sizeof() 是返回数据类型的内存占用字节,所以sizeof(a) 其实等价于其数值类型int 

    的占用内存。同样,sizeof(3) sizeof(5) 的返回值一样都为int类型的内存字节。

    sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用,我们来看一个完整的例子:

  char foo()

  {

  printf("foo() has been called.\n");

  return 'a';

  }

  int main()

  {

  size_t sz = sizeof( foo() ); // foo() 的返回值类型为char,所以sz = sizeof(char ),foo()并不会被调用

  printf("sizeof( foo() ) = %d\n", sz);

  }

       C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值,即下面这些写法都是错误的:

  sizeof( foo );// error

  void foo2() { } //函数返回类型为空指针,不确定

  sizeof( foo2() );// error

       C语言基本数据类型 ,int   long int  float double 等,这些数据类型的sizeof()返回值与特定的系统平台相关,比如在16位的平台下,sizeof(int) 为2,在32位平台下,它位4.

      所以不确定当前的数据类型占用字节时,可以用sizeof 算一下。

       .指针变量的sizeof

  学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),可以预计,在将来的64位系统中指针变量的sizeof结果为8。

  char* pc = "abc";

  int* pi;

  string* ps;

  char** ppc = &pc;

  void (*pf)();//函数指针

  sizeof( pc ); // 结果为4

  sizeof( pi ); // 结果为4

  sizeof( ps ); // 结果为4

  sizeof( ppc ); // 结果为4

  sizeof( pf );// 结果为4

  指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等,所以MFC消息处理函数使用两个参数WPARAM、LPARAM就能传递各种复杂的消息结构(使用指向结构体的指针)。

       数组的sizeof

      数组是内存中连续的一块单元,它的每一个元素所在的内存单元地址是相邻的连续的,数组名代表这块内存的首地址,这样才可以通过首地址调用所有元素。

      此时,sizeof  数组元素所在连续内存单元存放的数据类型的累加和。

      char  a[ ]="hello";

      sizeof(a) =6; 末尾还有一个空字符。

     int  a1[5] ;

      sizeof(a1)= 4*5=20;

      有时候可以通过sizeof确定数组元素个数=sizeof(a1)/sizeof(int);

      函数指针类型的形参的sizeof

     void fun(int arr[4])

    {

       int len=sizeof(arr);

     }

   有人会认为len=16;但sizeof(arr)=4;

   为什么会这样呢?

   在C语言函数的传参中,有传值和传地址两种方式。

   而上面函数的形参的定义形式编译器默认是传的数组的地址,既数组的首地址

  void main()

  {

      int a[4[={1,2,3,4};

     fun(a[4]);    此时传给函数形参的只是数组a的地址,与元素的值无关,所以也可以写为 fun(a) 或fun(a[0]);

  }

   当编译器读到fun(int arr[4])这一行时,并不关心【】的内容,它默认将arr[] 当做一个指针,用来保存传给它的数组的地址。所以 [ ]是个摆设,int arr[ ] 和int  arr[4] ,int arr[10], int * arr都是等价的。

  因此,arr是指针变量,sizeof (arr) 也就等于4了。

   结构体的sizeof

   首先得了解一下结构体的内存分配原则

     不得不提一个术语 :内存对齐

    所谓内存对齐是相对于CPU的访问内存说的。

    struct A

{

    char a;

    int  b;

     char c;

}

  这个结构体sizeof(a)的返回值为12,而不是三种类型的空间相加 1+4+1=6;

   为什么呢?

    这就关系到CPU从内存中读取的方式了。

    从开始的8位机,8080时代,数据总线的带宽为8位,所以,CPU每次从内存中最多可以读取8位(1个字节)

    以后的8086,数据总线扩到了16位,地址总线也到了20位,其寻址能力达到1M,由于CPU内部数据一次最多

处理16位,对于20位宽的地址,用十六位是表示不完全的,因此,后来Inter 工程师发明的内存地址=段地址*16+偏移地址   通过此方法,就可以表示出全部的1M 内存空间单元的地址。通常扩展带宽都是 8位,16位,32位   ,64位等,都是2的某次方,以为最初的计算机的指令是1个字节8位,从而奠定了,后来计算机的数据单元都是以字节位基础的,而每一次性能扩展都应该是8的倍数,这很好理解。但偏偏8086弄了个不伦不类的20位地址总线,当然它也想一次弄32位的,迫于当时CPU的技术有限(8086有40根引线),如果是16位的地址总线,寻址能力最大为64KB,显然有点不能满足当时内存以及其他外设的地址分配,因而Inter 把地址总线带宽扩到了20位,这样,从过把一个16位二进制数移四位达到20位,因为是移位,所以它的低四位都是0,也就相当于把一个16进制的数移一位,以移位后的20位数(二进制)作为段地址,再加上一个偏移地址,便得到一个20位的内存地址,关于CPU寻址,可以通过计算机组成原理了解一下,有详细的解释。这样的话就把内存分成了好几个段,每个段是连续的,为了在寻址前在CPU内部保存每个段的段基址,8086在CPU里设计了几个段寄存器,用来存储每个段的段首地址,当进行寻址时,CPU取出段寄存器里的段基址再加上偏移地址(16位),通过在加法累计器里的累加,成为20位的实际内存地址,然后传给地址总线,而8086也成为80X86的鼻祖。

     而偏移地址是相对于每段的段基址来说的, 因为偏移地址是16位,每段地址范围为0到2的16次方64KB,

 加上段基址就为 段基址+0~段基址+0xffff.

     可以看到每个段的段地址为16的倍数(16位移到20位相当于乘以16),而每段的地址范围也为16的倍数,8的倍数, 而数据总线每次从内村传给CPU数据时,8的倍数,8088数据总线为8位(每次传一个字节),8086数据总线为16位,每次传2个字节。可以看到每段如果从段首址开始读数据的话,不论是每次8个(字节),16位(字),32位(双字),等都能恰好把一个段取完,不会浪费空间,假设段首址位0,段尾址位15,内存单元地址为0~15,如果从0开始读(每次8位),刚好读两次就读完且不会浪费空间,如果从1开始读,1~8的地址为一个字节,从9开始读的时候,9~15,为7位,不够8位,因此就会被浪费,所以CPU一般是偶数位读的,这样一来,读字节,字,双字,四字时,刚好占用空间位8的整数,不会剩余空间,也称为自然边界,如果读的地址跨越了自然边界,就会是CPU用两个总线周期读数,为了让CPU的效率不降低,便产生了内存对齐的方法。

     来看一下对内存对齐的解释,以结构体为例。

     为什么会有C++内存对齐

以下内容节选自《Intel Architecture 32 Manual》。

为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。

二、C++内存对齐规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

对齐规则:

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照 #pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

3、结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

4.各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。

5.各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节自动填充。

6.同时为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

三、pragma pack 宏

VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。下面举例说明其用法。

  1. #pragma pack(push) //保存对齐状态  
  2. #pragma pack(4)//设定为4字节对齐  
  3. struct test  
  4. {  
  5. char m1;  
  6. double m4;  
  7. int m3;  
  8. };  
  9. #pragma pack(pop)//恢复对齐状态 

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。

    再来看一下strlen()

     strlen 是一个函数, 原型为  extern unsigned int strlen(char *s);在Visual C++ 6.0中,原型为size_t strlen(const char *string);  

     它的运行机制是从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'为止,然后返回计数器值。   

  如果你只定义字符串没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止。

   strlen 与sizeof的区别

 

char * fun(char *str)
{
memset(str, 0, sizeof(str));  //用strlen和sizeof()有什么区别
...
return str;
}
int main(int argc, char* argv[])
{
char *a, b[400];
a = fun(b);
}
strlen()和sizeof()出来的长度是不一样的,但结果好像都一样,memset()有那么聪明吗?


sizeof 这个是在汇编里面就存在的一个指令,可以直接返回你要判断的变量战局的内存的大?gt;>?br>这个是在编译器就确定的,一个要注意的问题是,看下面的代码
char* str=new char[100]
sizeof(str)
这个可是会返回4哦,可不是你要的400
而 char str[100]
sizeof(str)是会返回400的。
但是,无论如何strlen()都是一个函数,它返回的是一个字符串的长度,也就是说从你给的字符串
首地址开始,一直到'\0'为止的这一段长度。
memset真的没有那么智能,但是它确实高效。


strlen 返回的是实际串长
sizeof 如果*str是指针则返回 char *的大小 如果是用数组声明 返回的是空间的大小
char *sz = "abcde";
char sz1[20] = "abcde";
cout<<sizeof(sz)<<endl;
cout<<strlen(sz)<<endl;
cout<<sizeof(sz1)<<endl;
cout<<strlen(sz1)<<endl;
输出:
4
5
20
5


memset(str, 0, sizeof(str));  //用strlen和sizeof()有什么区别?
答:用sizeof的话,只给str所指向的内存块连续4个字节清0;
    用strlen的话,是给str所指向的字符串全部清0;
     
    sizeof(str)返回str这个指针变量所占的内存字节数目;
    strlen(str) 返回str所指向的字符串的长度


sizeof()应该是编译时处理的。strlen是函数,不一样的


char * fun(char *str)
{
memset(str, 0, sizeof(str));  //sizeof(str))求得是指针str的大小,指针占空间是一
                                      //样的4个字节;str指向的是数组的首地址,这样相当于
                                     //将数组前四个元素至为‘\0’,用printf("%s")的话,遇
                                    //到第一个'\0',即退出。
                                   //如果用memset(str, 0, strlen(str));就得看运气了,
                                  //str指向数组b[400]首地址,b[400]没有显示初始化,
                                 //strlen是遇到'\0'退出,有可能b[0]就是'\0'
                                //strlen(str)的结果就为0,用printf("%s")就打印不出来了;
                                //strlen(str)也有可能是其他值,得看'\0'在b[400]的哪个位置了
return str;
}
int main(int argc, char* argv[])
{
char *a, b[400];
a = fun(b);
}


char * fun(char *str)
{
memset(str, 0, sizeof(str)); //sizeof(str))求得是指针str的大小,指针占空间是一
                                  //样的4个字节;str指向的是数组的首地址,这样相当于
                                 //将数组前四个元素至为‘\0’,用printf("%s")的话,遇
                                //到第一个'\0',即退出。
                               //如果用memset(str, 0, strlen(str));就得看运气了,
                              //str指向数组b[400]首地址,b[400]没有显示初始化,
                             //strlen是遇到'\0'退出,有可能b[0]就是'\0'
                            //strlen(str)的结果就为0,用printf("%s")就打印不出来了;
                           //strlen(str)也有可能是其他值,得看'\0'在b[400]的哪个位置了
return str;
}
int main(int argc, char* argv[])
{
char *a, b[400];
a = fun(b);
}


sizeof(str))求得是指针str的大小



strlen---------测“字符个数”(包括:不可见字符,如:空格等)
sizeof---------测“BYTE个数”


sizeof返回对象所占用的字节大小.
strlen返回字符个数.
在使用sizeof时,有一个很特别的情况,就是数组名到指针蜕变,
char Array[3] = {'0'};
sizeof(Array) == 3;
char *p = Array;
sizeof(p) == 1;


在传递一个数组名到一个函数中时,它会完全退化为一个指针

    

   

   

   

  

  

      

   

    

原创粉丝点击