美文网首页iOSDev.DeepiOS Dev.
被 const 修饰的变量真的是常量吗?

被 const 修饰的变量真的是常量吗?

作者: 酷酷的哀殿 | 来源:发表于2016-09-17 22:49 被阅读731次

因为常年从事 iOS 开发,很多 c 语言的相关知识并没有机会深入了解。最近的加班比较少,特地整理了一篇关于 const 的文字以飨读者。

本文将会简单地通过示例代码 猜测 llvm 的编译处理过程。

被 const 修饰的局部变量

让我们通过一份实例代码来快速验证一下,const 修饰的变量真的是常量吗?

void test0() {
  const int const_int_0 = 9;  // 声明并定义常量

  const int *j = &const_int_0;  // 声明并定义指向常量的指针
  int *k = (int *)j;  // 强制转换指针类型
  *k = 200; // 修改指向区域的值

  printf("%d\n", const_int_0); // 输出 9
  printf("%d\n", *j); // 输出 200
}

仔细读一遍上面的代码,很容易产生两个疑惑。

  1. const 修饰的值是否能够被修改?
  • 如果被修改了,直接打印时,为什么仍然输出初始值?

关于第一个疑惑,实际上,const 修饰的值,只会在编译期保证其值不被修改。程序运行时,通过特定方法是可能修改值的(修改常量的值十分危险,下面会举例说明)
上面的代码通过强制转换的方式修改了常量的值。

下面,开始解答第二个问题。
在编译时,编译器发现 const_int_0真·常量。所以,会把后面使用到该常量的地方直接进行替换。printf("%d\n", const_int_0);printf("%d\n", 9);
也就是起到了类似于 define 的作用。
下面是该函数在 -OS 下的汇编代码。(发布应用时,默认的优化选项为-OS,含义为:在尽可能的不加大代码体积的情况下,尽可能的优化代码)

// 调用 _printf 前需要准备一些参数。 
// 寄存器 r0 保存第一个参数="%d\n",寄存器 r1 保存第二个参数=需要打印的值

 .private_extern _test0
 .globl _test0
 .align 1
 .code 16
 .thumb_func _test0
_test0:
Lfunc_begin0:
 .loc 1 13 0
 .cfi_startproc
 push {r4, r7, lr} //先保存后面用到的寄存器的值,该函数调用结束后,会恢复这些值。
 add r7, sp, #4 r7 = sp +4
Ltmp0:
 .loc 1 20 3 prologue_end
 movw r4, :lower16:(L_.str-(LPC0_0+4))
 movs r1, #9 // 寄存器r1 的值为9,第一次打印的值,编译后为常量
 movt r4, :upper16:(L_.str-(LPC0_0+4))
LPC0_0:
  add r4, pc // r4 = pc+r4;
 mov r0, r4 // r0 = r4
 blx _printf //打印
 .loc 1 20 3
 mov r0, r4 //r0 = r4
 movs r1, #200 // 寄存器r1 的值为200,第二次打印的值,编译器分析代码后,发现该值也为常量
 blx _printf //打印
 .loc 1 21 1 
 pop {r4, r7, pc} //出栈,函数结束
Ltmp1:
Lfunc_end0:
 .cfi_endproc

既然提到了真·常量,自然也有假·常量。下面的代码,会展示假·常量的两种情况。希望读者能自行体会之间的区别。

void test0_0() {

  const int const_int_0 = arc4random_uniform(100);   // 声明并通过随机数的方式初始化常量
  printf("%d\n", const_int_0);  // 输出72,该值为随机数

  const int *j = &const_int_0;  // 声明并定义指向常量的指针
  int *k = (int *)j;  // 强制转换指针类型
  *k = 200;  // 修改指向区域的值

  printf("%d\n", const_int_0);  // 输出 200
  printf("%d\n", *j);  // 输出 200
}

void test0_1(const int const_int_0) {

  printf("%d\n", const_int_0);  // 输出11,该值为函数的入参

  const int *j = &const_int_0;  // 声明并定义指向常量的指针
  int *k = (int *)j;  // 强制转换指针类型
  *k = 200;  // 修改指向区域的值

  printf("%d\n", const_int_0);  // 输出 200
  printf("%d\n", *j);  // 输出 200
}

被 const 修饰的全局变量

上面曾提及修改常量的值十分危险,下面这份代码展示了一种危险的操作情况。

const int const_int_1 = 9;// 声明并定义常量
void test1() {
  const int *j = &const_int_1;  // 声明并定义指向常量的指针
  int *k = (int *)j;  // 强制转换指针类型
  *k = 200;//  修改指向区域的值 
  // 该行代码会产生 `EXC_BAD_ACCESS` 错误。错误代码: `KERN_PROTECTION_FAILURE` (禁止写操作)

  printf("%d\n", const_int_1);
  printf("%d\n", *j);
}

实际上,编译器检测到全局的真·常量后,会把它放到只读区,当尝试修改内容时,系统会抛出异常。

 .private_extern _const_int_1    @ @const_int_1
 .section __TEXT,__const
 .globl _const_int_1
 .align 2
_const_int_1:
 .long 9                       @ 0x9

那么,该如何避免它被放到只读区域呢?
很简单,通过 __attribute__((section(...))) 显式地指定编译后所在的区域即可。

const int const_int_0   __attribute__((section("_DATA,myConst"))) = 1119;   // 声明并定义常量

相关文章

  • C++ const

    修饰成员变量 const修饰指针变量时: (1)const出现在星号左边,表示被指物是常量 (2)const位于*...

  • const、static

    const关键字 1.被const修饰的变量是常量,以下都是表示a是常量:const int a = 10;int...

  • 被 const 修饰的变量真的是常量吗?

    因为常年从事 iOS 开发,很多 c 语言的相关知识并没有机会深入了解。最近的加班比较少,特地整理了一篇关于 co...

  • C++ const常量

    Const是常量的意思,被其修饰的变量不可修改 如果修饰的是类、结构体,那么其成员变量也不可修改 Const修饰的...

  • iOS开发常用关键字const,static,extern

    const const翻译成中文是常量,常量是不可变的。const作用: const用来修饰基本变量或指针变量。 ...

  • const & static & volatile

    1.const,static,volatile修饰变量代表什么意思? const:修饰的变量为常量,常量是不允许修...

  • 关键字const/static/extern/typedef/t

    1、const(常量---readonly) 作用: 用于修饰 右边 的基本变量或指针变量; 被修饰的变量只读...

  • C++: const详解

    一、const变量 const修饰一个变量,代表这个变量是个常量,不可改变: 二、const与指针 1. 指向常量...

  • 常量指针和指针常量

    const关键字用来定义常量,如果一个变量被const修饰,那么他的值就不能被改变。 常量指针 (常量指针是指针指...

  • const和指针

    关键字const用来定义常量,如果一个变量被const修饰,那么它的值就不能再被改变。 1. 修饰变量 const...

网友评论

  • 南栀倾寒:刚才看了程序员的自我修养,常量确实是被系统放在.rodata.段

本文标题:被 const 修饰的变量真的是常量吗?

本文链接:https://www.haomeiwen.com/subject/kzkwettx.html