字节对齐不慎引发的挂死问题

前言

之前程序是32位的,切到64位之后,一些隐藏的问题就暴露了。这不,一个由字节对齐导致的挂死问题就出来了。

字节对齐和64位

关于字节对齐,可参考《理一理字节对齐的那些事》,而之前也分享过另一个切64位之后出现的问题,有兴趣的可以查看《记64位地址截断引发的挂死问题》。

本文背景

本文出现的场景是,系统需要解析JSON文件,但是出现部分功能解析正常,部分挂死,并且32位程序正常,而64位程序挂死。鉴于原系统比较复杂,本文将会简化其过程,来看看到底是什么导致了挂死。
本文示例代码主要引自《一个超轻量级的JSON解析器》。

简化后示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//来源:公众号【编程珠玑】
//https://www.yanbinghu.com
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<string.h>
#pragma pack(1)
#include"cJSON.h"
#pragma pack()

/*省略部分代码,完整代码可查看附录部分*/
int main(void)
{
char *filename = "./test.json";
cJSON *pJson = NULL;
cJSON *pTemp = NULL;
pJson = prepare_parse_json(filename);
if(NULL == pJson)
{
printf("parse json failed\n");
return -1;
}
/*获取name值*/
pTemp = cJSON_GetObjectItem(pJson,"name");
printf("name is %s\n",pTemp->valuestring);
/*获取site值*/
pTemp = cJSON_GetObjectItem(pJson,"site");
printf("site is %s\n",pTemp->valuestring);

/*获取age值*/
pTemp = cJSON_GetObjectItem(pJson,"age");
printf("age is %d\n",pTemp->valueint);
/*记得释放相关内存*/
cJSON_Delete(pJson);
pJson = NULL;
return 0;
}

编译运行结果:

1
2
3
$ gcc -L. -o parseJson parseJson.c -lcjson
$ ./parseJson
Segmentation fault (core dumped)

在实际中我们通过GDB观察发现,在解析JSON内部查看JSON数据是完好的,但是调用完解析JSON之后,再去访问使用就不对了,并且我们发现,在不同的功能模块中,调用结果不一样,大部分模块调用并没有任何问题,而只有某个功能模块调用出现问题。

真相

到底是什么导致的呢?
问题的根源在于下面这几行代码:

1
2
3
#pragma pack(1)
#include"cJSON.h"
#pragma pack()

另外补充,cJSON结构体如下:

1
2
3
4
5
6
7
8
9
typedef struct cJSON {  //cJSON结构体
struct cJSON*next,*prev; /*后驱节点和前驱节点*/
struct cJSON *child; /*孩子节点*/
int type; /* 键的类型*/
char *valuestring; /*字符串值*/
int valueint; /* 整数值*/
double valuedouble; /* 浮点数值*/
char *string; /* 键的名字*/
} cJSON;

#pragma指令说明了按一字节对齐,而cJSON的头文件也在其中,那么就会导致里面的cJSON结构体按照1字节对齐,最终其结构体大小为56个字节而已经编译好的cjson库可并非如此,因此对于64位程序,它还是按照8字节对齐,结构体大小为64字节,而对于32位程序,按照4字节对齐,也是56字节。

同一个结构体的大小竟然在不同的代码中大小不一样!

最终也就出现了我们遇到的情况,64位程序由于库中申请结构体内存大小与外部调用不一样,最终导致挂死,而32位程序解析JSON正常。

总结

幸运的是,本文示例中能够很明显的能看到问题所在,但在实际项目中,如果头文件管理不规范,并且项目的产品多样,通过编译宏来隔开使用的头文件,就很难发现这样的问题。

思考

什么情况下需要1字节对齐呢?

附录

本文完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//来源:公众号【编程珠玑】
//https://www.yanbinghu.com
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<string.h>
#pragma pack(1)
#include"cJSON.h"
#pragma pack()
size_t get_file_size(const char *filepath)
{
/*check input para*/
if(NULL == filepath)
return 0;
struct stat filestat;
memset(&filestat,0,sizeof(struct stat));
/*get file information*/
if(0 == stat(filepath,&filestat))
return filestat.st_size;
else
return 0;
}

char *read_file_to_buf(const char *filepath)
{
/*check input para*/
if(NULL == filepath)
{
return NULL;
}
/*get file size*/
size_t size = get_file_size(filepath);
if(0 == size)
return NULL;

/*malloc memory*/
char *buf = malloc(size+1);
if(NULL == buf)
return NULL;
memset(buf,0,size+1);

/*read string from file*/
FILE *fp = fopen(filepath,"r");
size_t readSize = fread(buf,1,size,fp);
if(readSize != size)
{
/*read error*/
free(buf);
buf = NULL;
}

buf[size] = 0;
return buf;
}
cJSON *prepare_parse_json(const char *filePath)
{
/*check input para*/
if(NULL == filePath)
{
printf("input para is NULL\n");
return NULL;
}
/*read file content to buffer*/
char *buf = read_file_to_buf(filePath);
if(NULL == buf)
{
printf("read file to buf failed\n");
return NULL;
}
/*parse JSON*/
cJSON *pTemp = cJSON_Parse(buf);
free(buf);
buf = NULL;
return pTemp;
}
int main(void)
{
printf("%u\n",sizeof(cJSON));
char *filename = "./test.json";
cJSON *pJson = NULL;
cJSON *pTemp = NULL;
pJson = prepare_parse_json(filename);
if(NULL == pJson)
{
printf("parse json failed\n");
return -1;
}
/*获取name值*/
pTemp = cJSON_GetObjectItem(pJson,"name");
printf("name is %s\n",pTemp->valuestring);
/*获取site值*/
pTemp = cJSON_GetObjectItem(pJson,"site");
printf("site is %s\n",pTemp->valuestring);

/*获取age值*/
pTemp = cJSON_GetObjectItem(pJson,"age");
printf("age is %d\n",pTemp->valueint);
/*记得释放相关内存*/
cJSON_Delete(pJson);
pJson = NULL;
return 0;
}

守望 wechat
关注公众号[编程珠玑]获取更多原创技术文章
出入相友,守望相助!