1. 问题现象
程序中实现了如下类似的程序,调用get_id函数后,发现en的值变成了0。本地简单实现如下程序,unsigned short queue[32]不论是否定义,en的值都变成了0。
#include <stdio.h>
int get_id(unsigned int * id){ *id = 0x9; return 1; }
void main(){ int value; unsigned short groupid=0; unsigned short queue[32]={0}; unsigned char en =1; printf("old groupid %hu, en %hu\n", groupid, en); get_id(&groupid); printf("groupid: %hu, en %hu\n\n", groupid,en); return;
}
|
2. 排查发现一点异常:
main函数中调用get_id函数时,groupid是unsigned short,get_id需要的时参数类型时unsigned int *,这里不匹配。
那么如何导致en的值被改变。
3. 尝试gdb调试查看:
发现变量的内存位置并不是按照定义的顺序来申请的,实际顺序是en--->groupid--->queue[32],en和groupid的地址挨着。在get_id中对 0x7fffffffe21c的连续4个字节进行了负值,最终将0x7fffffffe21f字节被覆盖位了0。
(gdb) b get_id Breakpoint 1 at 0x40059e: file test.c, line 4. (gdb) run Starting program: /home/apan/sdk-git/ssh/patch-parse/a.out Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-164.el8.x86_64 old groupid 0, en 1
Breakpoint 1, get_id (groupid=0x7fffffffe21c) at test.c:4 4 *groupid = 0x9; (gdb) p &groupid $1 = (unsigned int **) 0x7fffffffe1b8 (gdb) n 5 return 1; (gdb) n 6 } (gdb) n main () at test.c:18 18 printf("groupid: %hu, en %hu\n\n", groupid,en); (gdb) p &en $2 = (unsigned char *) 0x7fffffffe21f "" (gdb) p &groupid $3 = (unsigned short *) 0x7fffffffe21c (gdb) p &queue[0] $4 = (unsigned short *) 0x7fffffffe1d0 (gdb) p &queue[31] $6 = (unsigned short *) 0x7fffffffe20e
|
4. 这个问题的概率出现原因分析
在最初get_id函数这么写的时候,其实没出现过en被覆盖问题。在几次调整函数内部实现才发生的。
将本地分析的程序进行简单调整,增加了groupid en的地址打印,发现en的值未被覆盖。
#include <stdio.h>
int get_id(unsigned int * groupid){ *groupid = 0x9; return 1; }
void main(){ int value; unsigned short groupid=0; unsigned short queue[32]={0}; unsigned char en =1; printf("old groupid %hu %p, en %hu %p \n", groupid,&groupid, en,&en); get_id(&groupid); printf("groupid: %hu, en %hu\n\n", groupid,en); ; return; } |
查看调用栈信息
(gdb) b get_id Breakpoint 1 at 0x40059e: file test.c, line 4. (gdb) run Starting program: /home/apan/sdk-git/ssh/patch-parse/a.out Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-164.el8.x86_64 old groupid 0 0x7fffffffe21e, en 1 0x7fffffffe1cf
Breakpoint 1, get_id (groupid=0x7fffffffe21e) at test.c:4 4 *groupid = 0x9; (gdb) n 5 return 1; (gdb) n 6 } (gdb) n main () at test.c:18 18 printf("groupid: %hu, en %hu\n\n", groupid,en); (gdb) p &groupid $1 = (unsigned short *) 0x7fffffffe21e (gdb) p &en $2 = (unsigned char *) 0x7fffffffe1cf "\001" (gdb) (gdb) p &queue[0] $3 = (unsigned short *) 0x7fffffffe1d0 (gdb) p &queue[31] $4 = (unsigned short *) 0x7fffffffe20e (gdb) p &en $5 = (unsigned char *) 0x7fffffffe1cf "\001" (gdb) p &groupid $6 = (unsigned short *) 0x7fffffffe21e
|
Gdb查看堆栈发现,虽然局部变量没有变化,但栈内的局部变量分布的顺序变了,变为groupid--->queue--->en。这样get_id还是会将0x7fffffffe220 0x7fffffffe221变为0,但是未影响到queue和groupid。
尽管局部变量没有变化,仅仅是程序一点小的变化,变量的顺序就发生了变化。
5. 总结
总的来看,程序中局部变量的顺序一般会按照定义顺序排列,但并不一定和定义的顺序一致。
在C程序中,堆栈中的变量顺序是由编译器决定的。通常情况下,编译器会按照变量声明的顺序将它们放置在堆栈上。然而,在某些情况下,编译器可能会进行优化以改进内存布局或提高性能。
以下是一些可能导致编译器对堆栈中变量顺序进行优化的情况:
数据对齐:编译器可能会根据硬件平台的要求对变量进行对齐,以确保它们位于适当的内存地址上。这可以提高访问速度和效率。
寄存器分配:编译器可能会尝试将频繁使用的变量存储在寄存器中,以提高访问速度。在这种情况下,这些变量可能不会按照声明的顺序出现在堆栈上。
指令调度:编译器可能会重新排列指令以减少指令流水线的停滞时间,从而提高执行速度。这可能会导致堆栈中变量的顺序发生变化。
内存优化:编译器可能会通过合并相邻的变量或调整变量的大小来优化内存使用。这可能会导致堆栈中变量的顺序发生变化。
需要注意的是,编译器的具体行为取决于所使用的编译器版本、目标平台以及编译选项。因此,在不同的环境和配置下,堆栈中变量的顺序可能会有所不同。