C语言 结构体指针强制转换

来源:互联网 发布:原装ubuntu的电脑 编辑:程序博客网 时间:2024/06/05 05:51

最近写c程序遇到的结构体指针强转的坑,记录一下.
是一个简单的菜单程序,程序用到链表,表中存储了9个不同命令.每次将用户输入的命令与表中存储的命令名作对比(遍历查询),然后执行相应功能.

总体结构:

linktable.h:定义通用模块化链表数据结构,以及相关操作linktable.c:实现头文件中定义的链表操作main.c:主函数

通用链表节点:(抽象定义)

typedef struct LinkTableNode{    struct LinkTableNode *pNext;//只有后继指针}tLinkTableNode;

链表:

typedef struct LinkTable{    tLinkTableNode *pHead;//表头    tLinkTableNode *pTail;//表尾    int SumOfNode;//节点数    pthread_mutex_t mutex;//互斥锁}tLinkTable;

数据节点:(通用链表的具体化)

typedef struct DataNode{    char* cmd;//命令名    char* desc;//命令描述    int (*handler)();//函数指针,指向具体功能函数    struct DataNode *pNext;//后继指针}tDataNode;

main.c片段:

tDataNode * FindCmd(tLinkTable *head, char *cmd)//函数:遍历链表head,查找与cmd符合的命令并返回该数据节点{    tDataNode *pNode = (tDataNode*)getLinkTableHead(head);    while(pNode != NULL)    {        if(strcmp(pNode->cmd, cmd) == 0)        {            return pNode;        }        pNode = (tDataNode*)getNextLinkTableNode(head, (tLinkTableNode*)pNode);    }    return NULL;}//对数据节点赋值,注意每个字段的摆放顺序要与定义的格式相匹配static tDataNode menu[] =        {                {"version", "menu program v2.5",NULL,(tLinkTableNode*)&menu[1]},                {"help", "this is help cmd!", Help,(tLinkTableNode*)&menu[2]},                {"add", "this is add cmd!", Add, (tLinkTableNode*)&menu[3]},                {"sub", "this is sub cmd!", Sub, (tLinkTableNode*)&menu[4]},                {"mul", "this is multi cmd!", Multi, (tLinkTableNode*)&menu[5]},                {"div", "this is divide cmd!", Divide, (tLinkTableNode*)&menu[6]},                {"pow", "this is power cmd!", Power, (tLinkTableNode*)&menu[7]},                {"time", "this is time cmd!", Time, (tLinkTableNode*)&menu[8]},                {"quit", "this is quit cmd", Quit, (tLinkTableNode*)NULL}        };//初始化,创建链表体,并把头尾分别指向menu[0]和menu[8]int InitMenuData(tLinkTable **ppLinkTable){    *ppLinkTable = CreateLinkTable();    (*ppLinkTable)->pHead = (tLinkTableNode*)&menu[0];    (*ppLinkTable)->pTail = (tLinkTableNode*)&menu[8];    (*ppLinkTable)->SumOfNode = 9;    return SUCCESS;}tLinkTable *head = NULL;int main(){    InitMenuData(&head);//将链表体指针head初始化    printf("Welcome!Use 'help' to get how to use this system.\n");    while(1)//无限循环    {        char cmd[CMD_MAX_LEN];        printf("input a cmd >");        scanf("%s", cmd);//读取用户输入命令        tDataNode *p = FindCmd(head, cmd);//查找        if(p == NULL)//若找不到,提示错误        {            printf("Wrong cmd!Use 'help' to get how to use this system.\n");            continue;        }        printf("%s ---- %s\n", p->cmd, p->desc);//输出该命令信息        if(p->handler != NULL)        {            p->handler();//调用该命令的功能函数        }    }}

看起来没有什么问题,运行,崩溃.进入调试,发现原因在于main片段的第11行

    pNode = (tDataNode*)getNextLinkTableNode(head, (tLinkTableNode*)pNode);

getNextLinkTableNode的功能是返回链表head中pNode节点的下一个节点.这里对参数pNode(第4行创建为tDataNode*类型)进行强制转换成tLinkTableNode *类型.

然而我们知道,强制转换是有可能出问题的,这就是问题所在.tLinkTableNode是一个通用的链表节点类型,其中只包含一个后继域 pNext,而tDataNode包含了四个成员:两个char数组指针cmd和desc,还有函数指针handler和后继pNext.如此一来强转必然出问题.

执行第11行前,调试的信息如图所示:
这里写图片描述
pNode的类型为tDataNode*,其cmd字段为0x406070(“version”),pNext字段为0x405030

进入11行后,调试信息发生如下变化:
这里写图片描述
pNext类型为tLinkTableNode*,其中pNext字段为0x406070.是不是很熟悉?没错,就是强制转换之前的cmd字段内容.也就是说,pNext本该指向0x405030,却因为强制转换而变成了0x406070.而这是一个未知的地址,也就是常说的”指针乱指”,如此一来发生错误是必然的!

可以看到,结构体数组的地址都是非常规整的,他们彼此相邻,每个结构体占0x10的地址空间,然而由于指针乱指到未知的地址0x406070,进入该地址后,pNext继续乱指到0x73726576.再进入这个地址,读取其cmd字段,系统报告发生段错误,程序终止.
这里写图片描述

那么正确的做法是什么呢?观察强制转换的内容,可以发现强转成tLinkTableNode*后,pNext的内容为tDataNode *的cmd内容,也就是定义结构体中的第一个字段,要想保持强转后不丢失后继指针,只有在定义结构体时将后继指针调整到第一个字段:

typedef struct DataNode{    struct DataNode *pNext;    char* cmd;    char* desc;    int (*handler)();}tDataNode;

同时对结构体数组赋值时也要注意顺序的调整,把后继指针的赋值放在第一位:

static tDataNode menu[] =        {                {(tLinkTableNode*)&menu[1],"version", "menu program v2.5",NULL},                {(tLinkTableNode*)&menu[2],"help", "this is help cmd!", Help},                {(tLinkTableNode*)&menu[3],"add", "this is add cmd!", Add},                {(tLinkTableNode*)&menu[4],"sub", "this is sub cmd!", Sub},                {(tLinkTableNode*)&menu[5],"mul", "this is multi cmd!", Multi},                {(tLinkTableNode*)&menu[6],"div", "this is divide cmd!", Divide},                {(tLinkTableNode*)&menu[7],"pow", "this is power cmd!", Power},                {(tLinkTableNode*)&menu[8],"time", "this is time cmd!", Time},                {(tLinkTableNode*)NULL,"quit", "this is quit cmd", Quit}        };

这样程序才能正常运行

原创粉丝点击