C语言 -- sizeof总结
sizeof的语法
1:sizeof是C语言的关键字,用来计算变量、常量或数据类型在当前系统中占用内存的字节数。„
2:sizeof不是函数,产生这样的疑问是因为sizeof的书写确实有点像函数。 在程序中,sizeof有两种写法:
1)用于数据类型:sizeof(数据类型)
2)用于变量名:sizeof(变量名);sizeof变置名 //不建议这样使用
代码示例:
#include<stdio.h>
int main() {
int i;
char str[20];
printf("sizeof(i) is %d\n", sizeof(i));
printf("sizeof i is %d\n", sizeof i);
printf("sizeof int is %d\n", sizeof(int));
//printf("sizeof int is %d\n", sizeof int);错误写法
printf("sizeof(str) is %d\n", sizeof(str));
printf("sizeof str is %d\n", sizeof str);
printf("sizeof str is %d\n", sizeof (char[20]));
//printf("sizeof str is %d\n", sizeof char[20]);错误写法
printf("sizeof(365) is %d\n", sizeof(365));
printf("sizeof(i love you) is %d\n", sizeof("i love you"));
}
结果:
注:最后一个字符串之所以是11,是因为在C语言中字符串后边会默认添加一个结束符,也就是'\0'
sizeof与strlen
sizeof是运算符,用来计算变量、常量或数据类型在当前系统中占用内存的字节数
strlen是函数,用于计算字符串的实际长度
代码示例:
char ch[20];
strcpy(ch, "i love you");
printf("sizeof(ch) is %d\n", sizeof(ch));//地址长度20,不变
printf("strlen(ch) is %d\n", strlen(ch));//字符串的实际长度,也就是i love you的长度,为10
结构体内存对齐
首先看一个例子:
struct S
{
char a;
int b;
char c;
};
我们先来计算一下这个结构体的大小,如果不存在内存对齐这个问题,按理说这个结构体应该占(1+4+1)6个字节;然而事实上它占了12个字节,为什么?我们需要解决下面几个问题。
1.为什么存在内存对齐
- 平台原因(移植问题):一些资料上是这样说的,“不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些特定地址处取某些特定的数据,否则就会抛出硬件异常”。也就是说在计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取的。
- 性能问题:正是由于只能在特定的地址处读取数据,所以在访问一些数据时,对于访问未对齐的内存,处理器需要进行两次访问;而对于对齐的内存,只需要访问一次就可以。
看上面的图,因为规定只能在特定地址处读数据,所以我们假设只能在4的倍数的地址处读数据,那么可以访问的地址分别为0、4、8、12、16等,这时我们给出刚才的结构体,在处理器访问第一个结构体元素(char) a 时,它会从0号下标的地址处访问a,而下个结构体元素 b 为int型,它占了4个字节,很显然,要访问它时需要先从0号下标的地址开始访问,之后还得取它后面3个字节的内容作为一部分,之后再从4号下标的地址处访问1个字节(因为第一个字节被第一个元素占用了),共同的内容组成了元素 b ,很明显,访问 b 时要进行两次访问,并且很麻烦。这时如果存放数据时内存对齐了,则只需访问一次就可以读取到 b 的内容。第二幅图就是内存对齐的情况,在访问每个数据时,都可以一次性访问完毕。
2.内存对齐后,怎么计算结构体的大小
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数=编译器默认的一个对齐数与该成员大小的较小值,在VS环境下默认值为8,在Linux环境下默认值为4。
3. 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,被嵌套的结构体对齐到其自身对齐数的整数倍处(结构体的对齐数就是其内部成员中最大的对齐数),此时结构体的整体大小就是所有最大对齐数(含被嵌套结构体的对齐数)的整数倍。
此时再看上边的图:
利用这四个规则就可以计算结构体的大小。这时参照上面的图中第二种情况来计算,对于上面的结构体,先定义了char类型的 a ,规则1说第一个元素在偏移量为0的地址处,即它对齐到0号地址处的位置,其占一个字节;第二个元素要对齐到自己对齐数的整数倍处,因为第二个元素的大小为int型,占4个字节,而VS默认对齐数是8,取最小的数,所以 b 的对齐数为4,规则2说要对其自身对齐数(4)的整数倍,而此时 a 占了一个字节,下个地址依次为2、 3 、4等,当走到4号地址处时,此时4能被4整除,所以第二个元素对齐在4号下标的地址处,占用4个字;接下来,第三个元素为char类型的 c ,c的大小为1,和8比较取小的数,所以 c 的对齐数为1,而此时 b 存放完之后的下个地址为9号地址,根据规则2,9是1的整数倍,所以 c 直接存放在9号开头的地址处。接下来,计算它们的大小,分别为1(char)+3(偏移量)+4(int)+1(char)=9,而正确的结果为12,原因在于规则3,规则3规定结构体的总大小为最大对齐数的整数倍,上面的3个元素中,它们的对齐数分别为0、4、1。所以最大对齐数为4,而结构体大小要整除最大对齐数,刚才计算出结构体的大小为9,很明显不能整除,所以结构体的大小为1(char)+3(偏移量)+4(int)+1(char)+3(偏移量)=12。
再来看第二个例子:
struct S1
{
char c1;
char c2;
int i;
};
struct S2
{
char c1;
struct S1 s3;
double d;
};
首先计算出结构体S1的大小为1(char)+1(char)+2(偏移量)+4(int)=8,S1的对齐数就是其结构体成员中最大的对齐数,即4,在结构体S2中,对齐数分别是0、4、8;首先 c1 对齐在0号地址的位置,其占用1个字节,根据规则4,嵌套的结构体S1要对齐到自身对齐数4的整数倍处,所以此时结构体对其在4号地址处,其大小是8个字节,之后,d 的对齐数为8,所以它要对齐到16号地址处,所以此时计算出S2的大小是1(char)+3(偏移量)+8(struct S1)+4(偏移量)+8(double)=24,结构体S2中个成员的对齐数分别为0、4、8,因为24是8的倍数,符合规则4。所以S2的总大小就是24个字节。
#pragma pack(n) 表示设置为n字节对齐。
sizeof易错点
先看一段代码:
#include<stdio.h>
#include<string.h>
struct student {
char name[20];
int age;
int ID;
char add[300];
};
int main() {
char str[21];
struct student stu;
memset(str, 0, sizeof(str));
memset(&stu, 0, sizeof(stu));
}
上边代码主要就是对一个字符数组和一个结构体进行初始化,但是有时候我们可能需要将初始化那两行代码给封装起来,如下:
struct student {
char name[20];
int age;
int ID;
char add[300];
};
//初始化数据
void InitData(char *str,struct student *stu) {
memset(str, 0, sizeof(str));
memset(&stu, 0, sizeof(stu));
}
int main() {
char str[21];
struct student stu;
InitData(str, &stu);
}
但是这样是有问题的
我们可以走一个测试,在main函数里输出字符串和结构体的长度和在初始化函数里输出字符串和结构体的长度,如下:
struct student {
char name[20];
int age;
int ID;
char add[300];
};
//初始化数据
void InitData(char *str,struct student *stu) {
//memset(str, 0, sizeof(str));
//memset(&stu, 0, sizeof(stu));
printf("sizeof(str) is %d\n", sizeof(str));
printf("sizeof(stu) is %d\n", sizeof(stu));
}
int main() {
char str[21];
struct student stu;
printf("sizeof(str) is %d\n", sizeof(str));
printf("sizeof(stu) is %d\n", sizeof(stu));
printf("\n");
InitData(str, &stu);
}
输出:
可以看到,在main函数里输出的分别是21和328,也就是字符串str和结构体student的长度,但是在初始化函数里输出的都是8,这是为什么呢?
我们可以回到sizeof的定义,它是用来计算变量、常量或数据类型在当前系统中占用内存的字节数,在main函数里计算的就是字符串类型的结构类型的字节数,而在初始化函数里计算的都是指针类型所占的字节数,在64位操作系统中,指针都是占8位,所以输出的都是8,如果出现这样的错误后果会非常严重,可能会造成内存泄漏的问题。
那么我们怎么在初始化函数中初始化我们的成员呢?
首先,在处理字符串这种情况,我们可以在main函数中将字符串的长度传进去,如果只传一个字符串的地址,那么子函数不知道该字符串的长度,所以就无法完成初始化
那么对于结构体如何处理的呢?这里可以养成一个良好习惯,就是对结构体进行初始化时候,直接用数据类型,不要用变量名。
代码示例如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct student {
char name[20];
int age;
int ID;
char add[300];
};
void InitData(char *pstr, int length, struct student *stu);
int main() {
char str[21];
struct student stu;
printf("sizeof(str) is %d\n", sizeof(str));
printf("sizeof(stu) is %d\n", sizeof(stu));
printf("\n");
InitData(str, sizeof(str), &stu);
}
//初始化数据
void InitData(char *pstr, int length, struct student *stu) {
memset(pstr, 0, length);
memset(stu, 0, sizeof(struct student));
}
这样就可以调用初始化函数,将主函数的字符串和结构体进行初始化。