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

最近写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}
        };

这样程序才能正常运行

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值