const变量可以修改么?

遇到了一个关于const修饰的变量值是否能修改问题,虽然我知道const变量在某些情况下可以通过指向它的指针来间接修改,但是对原理还是很模糊,今天就整理了一下。


下面通过三段小代码来验证一下
1)、直接修改const修饰的局部变量

#include <stdio.h>

int main(void)
{
    const int a = 5;
    a = 10;
    printf("a=%d", a);
}

可以看出在编译时期就出现了错误:

gcc -o const_test const_test.c
const_test.c: In function ‘main’:
const_test.c:6:7: error: assignment of read-only variable ‘a’
     a = 10;

2)、通过指针修改const修饰的局部变量

#include <stdio.h>

 int main(void)
 {
      const int i = 5;
      int *p = (int *)&i;
      *p = 10;

      printf("&i=0x%x \r\n",&i);
      printf("p=0x%x \r\n",p);
      printf("i=%d \r\n",i);
      printf("*p=%d \r\n",*p);
  }

编译正常,运行正常:

./const_test 
&i=0xfe7d240c 
p=0xfe7d240c 
i=10 
*p=10 

3)、通过指针修改const修饰的全局变量

#include <stdio.h>

const int i = 5;
int main(void)
{
      int *p = (int *)&i;
      *p = 10;

      printf("&i=0x%x \r\n",&i);
      printf("p=0x%x \r\n",p);
      printf("i=%d \r\n",i);
      printf("*p=%d \r\n",*p);
 }
 ```
编译通过,运行时出现了段错误。

./const_test
[1] 16302 segmentation fault ./const_test

---
想要搞清楚上面三段代码的区别,我们要首先理解编译期和运行期的概念。
编译期是指把你的源程序交给编译器编译的过程,最终目的是得到可执行的文件。编译期错误是编译期间就能被编译器捕捉到的错误,譬如定义一个过大数组,不过最常见的还是语法错误或者拼写错误。

运行期指的是你将可执行文件交给操作系统(输入文件名,回车)执行、直到程序执行结束的期间,执行的目的是为了实现程序的功能。

编译期分配内存并不是说在编译期就把程序所需要的空间在内存里面分配好,而是说在程序生成的代码里面产生一些指令,由这些指令控制程序在运行的时候把内存分配好。不过分配的大小在编译的时候就是知道的,并且这些存贮单元的位置也是知道的。而运行期分配内存则是说在运行期确定分配的大小,存放的位置也是在运行期才知道的。

---
有了以上的理解,我们再看一下三段代码的结果:
第一段代码直接修改const修饰的变量,编译器在编译期检查出语法错误;第二段和第三段代码通过指针修改变量值,相当于骗过编译器,可以成功编译。
那为什么第三段代码在运行时期出现问题呢?这其实跟变量存储的位置有关,具体如下:

c语言中const全局变量存储在只读数据段,编译期最初将其保存在符号表中,第一次使用时为其分配内存,在程序结束时释放。

而const局部变量(局部变量就是在函数中定义的一个const变量,)存储在栈中,代码块结束时释放。

在c语言中可以通过指针对const局部变量进行修改,而不可以对const全局变量进行修改。因为const全局变量是存储在只读数据段。

---
关于内存分配区域的五个段,可参考[这篇文章](https://blog.csdn.net/love_gaohz/article/details/41310597)
[![](https://i.loli.net/2019/07/19/5d31394f312aa61984.bmp)](https://i.loli.net/2019/07/19/5d31394f312aa61984.bmp)

代码段 --text(code segment/text segment)
text段是程序代码段,保存进程所执行的二进制文件。

数据段 -- data
data包含静态初始化的数据,所以有初值的全局变量和static变量在data区。

bss段--bss
bss段存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。

stack:
栈(stack)保存函数的局部变量(但不包括static声明的变量, static 意味着 在数据段data中 存放变量),参数以及返回值。是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶部在可读写的RAM区的最后。
判断栈的增长方向,可以在函数内部定义几个局部变量,通过打印变量的地址来观察(后进先出)。

heap:
堆(heap)保存函数内部动态分配内存,是另外一种用来保存程序信息的数据结构,更准确的说是保存程序的动态变量。堆是“先进先出”(First In first Out,FIFO)数据结构。它只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。

---
小扩展:
下面代码中各变量的存放位置:

int a = 0; 全局初始化区 
char *p1; 全局未初始化区 
main() 

int b; 栈 
char s[] = "abc"; 栈 
char *p2; 栈 
char *p3 = "123456"; 123456在常量区,p3在栈上。 
static int c =0; 全局(静态)初始化区 
p1 = (char *)malloc(10);  堆
strcpy(p1, "123456"); 123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 
}

以下两个区别:

char *s1 = "Hellow Word"; 是在编译时就确定的;
char s1[] = "Hellow Word"; 在运行时期确定;
```