下次再考sizeof我一定会!

大三菜鸡一个,在几场笔试里面每次都被sizeof虐,这次我不能忍了。

所有代码演示均在64位系统下

文章参考:百度

定义

sizeof是C/C++中的一个操作符(operator),简单的说其作用就是返回一个对象或者类型所占的内存字节数。

语法

sizeof的语法有两种形式

sizeof(类型)
sizeof 对象

比如

int i;
sizeof(i);//ok
sizeof i;//ok
sizeof(int);//ok
sizeof int;//error
sizeof(2);//2的类型为int,所以等价于sizeof(int);
sizeof(2+3.14);//3.14的类型为double,2也会被提升成double类型,所以等价于sizeof(double);

基本数据类型的sizeof 值得一提的是 long在32位和64位的sizeof结果不同

#include <stdio.h>

int main()
{
    printf("%d\n",sizeof(char));//32位结果为1 64位同
	printf("%d\n",sizeof(short));//32位结果为2 64位同
	printf("%d\n",sizeof(int));//32位结果为4 64位同
	printf("%d\n",sizeof(float));//32位结果为4 64位同
	printf("%d\n",sizeof(long));//32位结果为4 64位为8 
	printf("%d\n",sizeof(double));//32位结果为8 64位同
}

函数方面

#include <stdio.h>
char foo()
{
	printf("foo()hasbeencalled.\n");
	return 'a';
}
int main(void) { 
    char sz=sizeof(foo());
    //foo()的返回值类型为char,所以sz=sizeof(char),foo()并不会被调用
    printf("sizeof(foo())=%d\n",sz);
}

输出如下,我们可以发现foo并没有真正被调用,只会直接拿这个函数的返回类型

sizeof(foo())=1

改变一下

#include <stdio.h>
char foo()
{
	printf("foo()hasbeencalled.\n");
	return 1;
}
void foo1()
{
	printf("foo()1hasbeencalled.\n");
}
int main(void) { 
    printf("sizeof(foo())=%d\n",sizeof(foo()));
    printf("sizeof(foo1())=%d\n",sizeof(foo1()));
}

然后你会发现,不行

#include <stdio.h>
struct S
{
  int f1:1;
  int f2:5;
  int f3:12;
};
int main(void) { 
    int c = sizeof(S);
    printf("%d",c);	// 4
}

改变一下

#include <stdio.h>
struct S
{
  int f1:1;
  int f2:5;
  int f3:12;
};
int main(void) { 
    int c = sizeof(S.f1);
    printf("%d",c); //error
}

这个同样不行。
以上的原因是因为C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值

sizeof在指针的应用

指针在C里面是一个非常重要的概念,它记录了另一个对象的地址。在32位计算机中,一个指针变量的返回值通常是4(注意结果是以字节为单位),在64位系统中指针变量的sizeof通常为8。

#include <stdio.h>

int main()
{
    char *a = "codeMan";
    int *b;
    char **c = &a;
    void(*d)();//函数指针
    printf("%d\n",sizeof(a));//32位结果为4 64位结果为8
    printf("%d\n",sizeof(b));//32位结果为4 64位结果为8
	printf("%d\n",sizeof(c));//32位结果为4 64位结果为8
    printf("%d\n",sizeof(d));//32位结果为4 64位结果为8
}

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

数组的sizeof

数组的sizeof值等于数组所占用的内存字节数,跟数组的长度、类型有关

#include <stdio.h>
int main()
{
   	char a[] ="123";
	int b[3];
	double c[3];
	char d[10] ="123";
	printf("%d\n",sizeof(a));	//4
	printf("%d\n",sizeof(b));	//12	=>   4*3
	printf("%d\n",sizeof(c));	//24	=>	 4*4
	printf("%d\n",sizeof(d));	//10 不受内容影响
}

前面我们提到char在32位和64位的sizeof结果均为1,这里a不是123么,怎么会是长度会是4呢,因为字符末尾还存在一个’\0’终止符,本身数组长度就是4。

当你在函数里面传数组

#include <stdio.h>
void test(char a[3]){//or a[]
	printf("%d\n",sizeof(a));
}
int main(void) { 
   	char a[] ="123";
	test(a);	//64位下输出8
}

这是因为在函数调用中,参数的数组只是数组的起始地址罢了,也就是一个指针,前面我们提到32位下指针类型一律为4,64位一律为8,所以结果为8

被虐了无数次的结构体

结构体很绕,为什么呢?

因为存在一种东西叫做字节对齐,有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,以此类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。

字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2)结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;
3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

值得一提的是,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。

还是来一些例子吧

#include <stdio.h>
struct S
{
  int f1;
  int f2;
  int f3;
};
struct S1{
    char c1;
    S s;
    char c2;
};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 12
    int b = sizeof(S1);
    printf("%d\n",b);	//20
}

结构体S的最宽简单成员类型为int,int的sizeof为4,所以整个sizeof(S)为4*3=12
结构体S1,虽然里面有结构体S,但是我们的最简宽度应该是基本数据类型,所以是int,所以c1和c2补齐各为4,但是总的S1是要加上S的,所以结果为20

值得一提的是,空结构体的sizeof不为0,为1

#include <stdio.h>
struct S
{

};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 1
}

更虐的结构体含位域

位域啥意思?

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位(一个字节个二进制位)。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为位域,位域是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

来个例子

#include <stdio.h>
struct S
{
  int f1:4;		//
  int f2:6;
  int f3:8;
};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 4
}

分析:

上述中,我们让f1在一个字节里面占4位,f2占6位,f3占8位
而一个int会有4个字节,一个字节8位,也就是总共32位,而上述4+6+8<32
所以用一个int存储足够,所以sizeof(S)结果为4

改造一下

#include <stdio.h>
struct S
{
  int f1:16;
  int f2:8;
  int f3:16;
};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 8
}

分析

依照上面的分析f1和f2可以存在一个int里面,此时我们还想继续放f3,发现放不下了,所以我们只能再搞一个int出来,所以是两个int,所以结果为8

非位域字段穿插

#include <stdio.h>
struct S
{
  int f1:4;
  int f2;
  int f3:8;
};
int main(void) { 
    int a = sizeof(S);
    printf("%d\n",a);	// 12
}

分析

非位域字段穿插在其中,不会产生压缩

总结

  1. 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
  2. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
  3. 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
  4. 如果位域字段之间穿插着非位域字段,则不进行压缩;
  5. 整个结构体的总大小为最宽基本类型成员大小的整数倍。

总结

ps:引用自他人

理解 sizeof 只需要抓住一个要点:栈
程序存储分布有三个区域:栈、静态和动态。能够从代码直接操作的对象,包括任何类型的变量、指针,都是在栈上的;动态和静态存储区是靠栈上的所有指针间接操作的。 sizeof 操作符,计算的是对象在栈上的投影体积;记住这个就很多东西都很清楚了。

char const*static_string="Hello";
//sizeof(static_string)是sizeof一个指针,所以在32bitsystem是4
char stack_string[]="Hello";
//sizeof(stack_string)是sizeof一个数组,所以是6*sizeof(char)
char*string=newchar[6];
strncpy(string,"Hello",6");
//sizeof(string)是sizeof一个指针,所以还是4。
//和第一个不同的是,这个指针指向了动态存储区而不是静态存储区。

好了 下次考sizeof我一定会 如果这篇文章有帮到你 麻烦点个赞 让我收到你的支持 这是我分享知识的动力 谢谢

原创文章 14 获赞 52 访问量 1493

猜你喜欢

转载自blog.csdn.net/weixin_44233929/article/details/105607670