最近写c程序遇到的结构体指针强转的坑,记录一下.
是一个简单的菜单程序,程序用到链表,表中存储了9个不同命令.每次将用户输入的命令与表中存储的命令名作对比(遍历查询),然后执行相应功能.
总体结构:
linktable.h:定义通用模块化链表数据结构,以及相关操作
linktable.c:实现头文件中定义的链表操作
main.c:主函数
1
2
3
通用链表节点:(抽象定义)
typedef struct LinkTableNode
{
struct LinkTableNode *pNext;//只有后继指针
}tLinkTableNode;
1
2
3
4
链表:
typedef struct LinkTable
{
tLinkTableNode *pHead;//表头
tLinkTableNode *pTail;//表尾
int SumOfNode;//节点数
pthread_mutex_t mutex;//互斥锁
}tLinkTable;
1
2
3
4
5
6
7
数据节点:(通用链表的具体化)
typedef struct DataNode
{
char* cmd;//命令名
char* desc;//命令描述
int (*handler)();//函数指针,指向具体功能函数
struct DataNode *pNext;//后继指针
}tDataNode;
1
2
3
4
5
6
7
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();//调用该命令的功能函数
}
}
}
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
看起来没有什么问题,运行,崩溃.进入调试,发现原因在于main片段的第11行
pNode = (tDataNode*)getNextLinkTableNode(head, (tLinkTableNode*)pNode);
1
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;
1
2
3
4
5
6
7
同时对结构体数组赋值时也要注意顺序的调整,把后继指针的赋值放在第一位:
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}
};
1
2
3
4
5
6
7
8
9
10
11
12
这样程序才能正常运行
---------------------
作者:caisense
来源:CSDN
原文:https://blog.csdn.net/u012033124/article/details/72785484
版权声明:本文为博主原创文章,转载请附上博文链接!