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

作者:守望,Linux应用开发者,目前在公众号【编程珠玑】 分享Linux/C/C++/数据结构与算法/工具等原创技术文章和学习资源。

前言

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

字节对齐和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
"
);
        return -1;
    }
    /*获取name值*/
    pTemp = cJSON_GetObjectItem(pJson,"name");
    printf("name is %s
"
,pTemp->valuestring);
    /*获取site值*/
    pTemp = cJSON_GetObjectItem(pJson,"site");
    printf("site is %s
"
,pTemp->valuestring);

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

编译运行结果:

$ 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字节对齐呢?

附录

本文完整代码可点击阅读原文或者访问
https://www.yanbinghu.com/2019/08/04/11325.html
附录部分。


●编号551,输入编号直达本文

●输入m获取文章

C语言与C++编程

分享C/C++技术文章

官方公众号