讨论一个轻松点的话题→
嵌入式开发面试中大家应该经常被问到在 C 语言中一个用 const 修饰的变量是否可以被修改,然后面试者回答:“可以”,面试官说:“你可以走了”。那么,恭喜你躲过一劫。好了,不开玩笑了。那如何回答关于 const 是否可以被修改的问题呢?我们直接动手试一下就知道了。
环境: Linux-4.9.151,gcc 7.5.0
- 全局的初始化的 const 变量:
const int my_const = 10;
int main()
{
return 0;
}
objdump -t 可以将目标文件中变量属于哪个段的内容打印出来:
objdump -t a.out | grep my_const
0000000000000694 g O .rodata 0000000000000004 my_const
可以看到 my_const 在 rodata 段,因此这个变量无论如何都是不能被修改的。
- 全局的未初始化的 const 变量:
const int my_const;
int main()
{
return 0;
}
objdump -t 可以将目标文件中变量属于哪个段的内容打印出来:
objdump -t a.out | grep my_const
0000000000201014 g O .bss 0000000000000004 my_const
可以看到未初始化的 my_const 在 bss 段,那么这个变量是否可以修改呢?我们先直接对其进行修改:
const int my_const;
int main()
{
my_const = 2;
return 0;
}
我们在使用 gcc 编译时发现 gcc 报错了:
gcc test.c
test.c: In function 'main':
test.c:7:14: error: assignment of read-only variable 'my_const'
my_const = 2;
^
那有没有办法绕过 gcc 的检查呢?这是可以的,我们直接对其地址进行修改:
const int my_const;
int main()
{
int *point = (int *)&my_const;
*point = 2;
printf("my_const is %d\n", my_const);
return 0;
}
./a.out
my_const is 2
可以看到对全局未初始化的 const 变量地址进行操作,完全可以重写 const 变量中的内容。
- 局部的未初始化/初始化的 const 变量:
int main()
{
const int my_const0 = 1;
const int my_const1;
return 0;
}
通过 objdump 我们找不到 my_const0 和 my_const1,他们既不在 rodata 段也不在 bss 段。
但是我们通过对比发现,当定义这两个局部 const 变量后,text 段的大小变大了,也就是说这两个变量在 text 段中,当然这个说法不完全正确,应该说在函数运行时,局部变量会被压入栈,所以上述的 const 变量是在栈中被修改的。
那么我们试着通过地址对其进行修改:
int main()
{
const int my_const0 = 1;
const int my_const1;
int *point = (int *)&my_const0;
*point = 2;
point = (int *)&my_const1;
*point = 3;
printf("my_const0 is %d, my_const1 is %d\n", my_const0, my_const1);
return 0;
}
./a.out
my_const0 is 2, my_const1 is 3
我们看到这两个局部的初始化和未初始化的 const 的变量都是可以被修改的。
所以我们总结一下:
- 全局初始化的 const 变量不能被修改;
- 未初始化的 const 变量和局部 const 变量可以被修改,但必须通过地址对其进行修改;