文章目录
今天继续分享自己看到的一个c语言小知识点,代码如下:
1.demo
实验环境:
Intel x86_64
ubuntu 20.04
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct test{
short a;
int b;
};
int main()
{
struct test test_1 = {
.a = 1,
.b = 2,
};
struct test test_2 = {
.a = 1,
.b = 2,
};
if(memcmp(&test_1, &test_2, sizeof(struct test)) == 0){
printf("test_1 == test_2\n");
}else{
printf("test_1 != test_2\n");
}
return 0;
}
上述结果相等吗?
2.memcmp函数
函数原型如下:
#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
作用:比较内存区域s1和s2的前n个字节(每个字节都被解释为unsigned char)。
3.测试分析
结果显示不相等。
这是为什么了?
因为结构体的每一个成员都要能够自然对齐,如果没有对齐那就要进行填补。
对齐的好处:简化了处理器和内存系统之间接口的硬件设计,提高内存系统的性能。
x86_64架构 对齐的原则:任何K字节的基本对象的地址必须是K的倍数。
比如:一个short对象的起始地址一定是2的倍数,一个int对象的起始地址一定是4的倍数。
注意:我这里只是指x86_64架构下对齐原则。
由于要自然对齐,所以编译器要在short后面填充两个字节,填充的字节的值是多少呢?
那么我们写一个简单的例子来测试一下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct test{
short a;
int b;
};
int main()
{
struct test test_1 = {
.a = 11,
.b = 22,
};
struct test test_2 = {
.a = 11,
.b = 22,
};
char *p1 = (char *)&test_1;
for(int i = 0; i < sizeof(struct test); i++){
printf("%10d ", *p1);
p1++;
}
printf("\n");
char *p2 = (char *)&test_2;
for(int i = 0; i < sizeof(struct test); i++){
printf("%10d ", *p2);
p2++;
}
printf("\n");
return 0;
}
结果如下:
我测试多次,由于是局部变量,局部变量如果没有初始化,那么程序运行中调用该函数时会给其初始化为一个随机值,所以填充的字节值也是随机的,那么填充的这两个字节的值大小不确定,那我们调用memcpy进行结构体比较时,每个字节都要进行比较,结果当然就不相等。
修改方案1:
当我把第一个成员由short改为int后,结构体已经自然对齐了,不用再填充字节,所以这次比较就会相等。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct test{
int a;
int b;
};
int main()
{
struct test test_1 = {
.a = 1,
.b = 2,
};
struct test test_2 = {
.a = 1,
.b = 2,
};
if(memcmp(&test_1, &test_2, sizeof(struct test)) == 0){
printf("test_1 == test_2\n");
}else{
printf("test_1 != test_2\n");
}
return 0;
}
修改方案2:
将 struct test test_1 和 struct test test_2结构体改为全局变量:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct test{
short a;
int b;
};
struct test test_1 = {
.a = 11,
.b = 22,
};
struct test test_2 = {
.a = 11,
.b = 22,
};
int main()
{
if(memcmp(&test_1, &test_2, sizeof(struct test)) == 0){
printf("test_1 == test_2\n");
}else{
printf("test_1 != test_2\n");
}
char *p1 = (char *)&test_1;
for(int i = 0; i < sizeof(struct test); i++){
printf("%-10d ", *p1);
p1++;
}
printf("\n");
char *p2 = (char *)&test_2;
for(int i = 0; i < sizeof(struct test); i++){
printf("%-10d ", *p2);
p2++;
}
printf("\n");
return 0;
}
当把 struct test test_1 和 struct test test_2结构体改为全局变量后,由于Linux c中规定未初始化的全局变量,运行时会将其初始化为0,所以填充的两个字节的值也是0,那么这种情况下就可以用 memcmp 比较了。
linux系统中:未初始化的全局变量属于可执行文件(ELF格式)的bss段,bss段不占用磁盘空间,仅仅是一个占位符,这样可以节省磁盘空间大小。在运行时,系统将bss段全部初始化为0。
结论
最好不要用memcmp函数来比较结构体的大小。
言外之意:最好不要通过一个一个字节的方式来比较结构体的大小,因为体系架构对齐要填充字节的缘故,很有可能会填充垃圾信息在结构体中。
参考资料
深入理解计算机系统
Linux环境编程从应用到内核