一直对const修饰的变量理解不透彻,今天结合汇编来了解了解。首先,我的理解是const修饰的变量是只读的,那么它肯定位于只读数据段。其次,有人说const变量在声明时必须初始化,至于为什么,我想是因为一个不可修改的变量如果不对其初始化等到某处程序用它的时候没有任何意义,此时它被置于BSS段。
C代码:
- const int a;
- int b=2;
- int main(void)
- {
- return 0;
- }
ARM汇编:
- .file "abc.c"
- .global b
- .data
- .align 2
- .type b, %object
- .size b, 4
- b:
- .word 2
- .text
- .align 2
- .global main
- .type main, %function
- main:
- @ Function supports interworking.
- @ args = 0, pretend = 0, frame = 0
- @ frame_needed = 1, uses_anonymous_args = 0
- mov ip, sp
- stmfd sp!, {fp, ip, lr, pc}
- sub fp, ip, #4
- mov r3, #0
- mov r0, r3
- sub sp, fp, #12
- ldmfd sp, {fp, sp, lr}
- bx lr
- .size main, .-main
- .comm a,4,4
- .ident "GCC: (GNU) 4.1.2"
在linux系统中,BSS段的数据好像是被置0.如果该const变量初始化了,那么它会位于只读数据段。
const变量如果不加类型限定,在gcc编译下,是被当做int(也许是long?)处理的,也许其他编译器有不同的处理方式。 下面的C代码给a一个负的浮点数,编译器会把它强制转换为int
- const a=-5.01;
- int b=2;
- int main(void)
- {
- return 0;
- }
ARM汇编:
- .global a
- .section .rodata
- .align 2 //4字节对齐,说明是int
- .type a, %object
- .size a, 4
- a:
- .word -5 //有符号
指针只读变量(指针常量)、只读变量指针(常量指针)
其实不能说是常量,只是听着顺口。关于const的修饰问题,让人有点犯晕。
- #include <stdio.h>
- const int a = 1;
- int const b = 2;
- const int *pa = NULL;
- int const *pb = NULL;
- int * const pc = NULL;
- int main(void)
- {
- return 0;
- }
ARM汇编
- .global a
- .section .rodata
- .align 2
- .type a, %object
- .size a, 4
- a:
- .word 1
- ########################
- .global b
- .align 2
- .type b, %object
- .size b, 4
- b:
- .word 2
- ########################
- .global pa
- .bss
- .align 2
- .type pa, %object
- .size pa, 4
- pa:
- .space 4
- ########################
- .global pb
- .align 2
- .type pb, %object
- .size pb, 4
- pb:
- .space 4
- ########################
- .global pc
- .section .rodata
- .align 2
- .type pc, %object
- .size pc, 4
- pc:
- .space 4
1-7行是对变量a的描述,9-14是变量b的描述,两者都是位于:.section .rodata只读数据段,a,b属性都一样,由此可见 const int a和int const a是一回事,同理const int *pa和int const *pb也是一回事,但是pa和pb在内存位置却是在BSS段。再看int * const pc ,pc位于rodata段。看来int const *pa和int * const pa 是不一样的意义。那么什么事BSS段,什么是rodata?
在采用段式内存管理的架构中(比如intel的80x86系统),bss段(Block Started by Symbol segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时bss 段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了。
比如,在C语言之类的程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。在《Programming ground up》里对.bss的解释为:There is another section called the .bss. This section is like the data section, except that it doesn’t take up space in the executable.
text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化,这个时候才真正分配内存空间。
那么BSS段的变量符合:1.全局变量 2.未初始化。但是在C程序中int const *pa = NULL ;const int *pa = NULL;貌似pa和pb已经初始化了?分一下段 int const *pa = NULL 原来const修饰的是*pa 而不pa,NULL只是当做一个整型的0而不是空指针。这个立即数0(NULL)的内存地址此时还是未知,相当于未初始化,所以pa理所当然的分配到BSS段。
int * const a = NULL, const修饰的是int *类型的指针变量a 指向 NULL(0地址),a是个指针,暂且说是地址变量,const修饰的地址变量a,这就是指针只读变量(指针常量).那么a里面的内容不可变,也就是它的指向地址是不可变的,地址里面的内容(*a)是可变的。就好比出租房,房的钥匙是不能变的,要不然就打不开门,至于里面住谁,给我租金就行。
int const *a = NULL,const修饰的是*a ,一个整型变量,那么这就是只读变量指针(常量指针),这个指针指向一个常量,指针本身是可被修改的(修改了仍旧需要指向一个只读变量),但是它指向的内容不可修改。就好比自己家里的房子,可以换锁,但是不能换人,换人了房子就不是你的了。
那么const变量有些什么作用呢?粗略总结一下:
①保证数据不会被意外修改:
记得当初写单片机程序点数码管时,0-9的段码被定义为一个数值,const char segcode[]={0x$$,0x$$,.......};这样段码就永远不会被意外修改,因为没有修改权限(.rodata段)。如果简单的声明为char segcode[]={0x$$,0x$$,......};如果刚好它附近另一个数组不小心越界了,那么就会导致segcode数据被修改。如下C代码
- #include <stdio.h>
- char over[5]="";
- char segcode[]={0x11,0x12,0x13};
- int main(void)
- {
- over[6] = 0x1f;
- printf("%x %x %x\n",segcode[0],segcode[1],segcode[2]);
- return 0;
- }
明显对over的访问越界了,之前我认为打印出来的是0x11 0x12 0x13,其实:
[root@localhost abcc]# ./abc
11 1f 13
觉得很有意思,原来数据段的变量会依次在内存中对齐排列,over地址如果是0x10,那么over[6]就是0x10+6=0x1f,segcode是0x10+5=0x1e;over[6]和segcode[1]是一回事。
②可以避免子函数的传参不被修改
例如 glic里面c库中的strcpy原型 char *strcpy(char *dest, const char *src);其实和上面意义差不多,只是一种用法而已。
看到有人提出修改const变量的方法
- const int a = 1;
- int main(void)
- {
- int *p = (int *)&a;
- *p = 3;
-
- return 0;
- }
gcc是4.1.2的编译是没问题,但是运行段错误,明显对于不同的编译器会有不同的结果,去修改这样一个变量也不知道有没有意义。而且好像定义一个局部的const变量都会被gcc优化,变成一个普通局部变量,要不然每次调用这个函数,重新在rodata生成数据,显然这是不可能的,rodata是不可能动态分配的,它在镜像中就固定了,所以局部const变量好像没任何意义,会被优化。
阅读(2024) | 评论(0) | 转发(0) |