来源:公众号【编程珠玑】
作者:守望先生
网站:https://www.yanbinghu.com
之前程序是32位的,切到64位之后,一些隐藏的问题就暴露了。这不,一个由字节对齐导致的挂死问题就出来了。
关于字节对齐,可参考《理一理字节对齐的那些事》,而之前也分享过另一个切64位之后出现的问题,有兴趣的可以查看《记64位地址截断引发的挂死问题》。
本文出现的场景是,系统需要解析JSON文件,但是出现部分功能解析正常,部分挂死,并且32位程序正常,而64位程序挂死。鉴于原系统比较复杂,本文将会简化其过程,来看看到底是什么导致了挂死。 本文示例代码主要引自《一个超轻量级的JSON解析器》。
//来源:公众号【编程珠玑】
//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 ;
}
编译运行结果:
$ gcc -L. -o parseJson parseJson.c -lcjson
$ ./parseJson
Segmentation fault (core dumped)
在实际中我们通过GDB观察发现,在解析JSON内部查看JSON数据是完好的,但是调用完解析JSON之后,再去访问使用就不对了,并且我们发现,在不同的功能模块中,调用结果不一样,大部分模块调用并没有任何问题,而只有某个功能模块调用出现问题。
到底是什么导致的呢? 问题的根源在于下面这几行代码:
#pragma pack(1)
#include"cJSON.h"
#pragma pack()
另外补充,cJSON结构体如下:
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字节和1字节对齐,都是36字节,因此也不会有问题。
同一个结构体的大小竟然在不同的代码中大小不一样!
最终也就出现了我们遇到的情况,64位程序由于库中申请结构体内存大小与外部调用不一样,最终导致挂死,而32位程序解析JSON正常。
幸运的是,本文示例中能够很明显的能看到问题所在,但在实际项目中,如果头文件管理不规范,并且项目的产品多样,通过编译宏来隔开使用的头文件,就很难发现这样的问题。
什么情况下需要1字节对齐呢?
本文完整代码可点击阅读原文或者访问 http://www.yanbinghu.com/2019/08/04/21364.html 附录部分。