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

因为常年从事 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;   // 声明并定义常量
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • 1.语言中变量的实质 要理解C指针,我认为一定要理解C中“变量”的存储实质, 所以我就从“变量”这个东西开始讲起吧...
    金巴多阅读 1,765评论 0 9
  • 注:这是第三遍读《C语言深度解剖》,想想好像自从大学开始就没读完过几本书,其中谭浩强的那本《C语言程序设计(第四版...
    HavenXie阅读 1,723评论 1 6
  • 泪湿鲛绡透,蹙眉终难舒,浑浑噩噩度春秋,空废水东流,泪尽人空瘦,终究一场空,似梦游。题罢千山云岫,雁空愁,说与谁人...
    叶落岁暮阅读 401评论 6 4
  • 嘀嗒 日出,九点 嘀嗒 日落,十八点 嘀嗒 睡了 嘀嗒 醒了 滴滴答答 哇,哇 滴滴滴,哒哒哒 呜呜,呜呼!
    杨鼎玉阅读 265评论 0 0
  • 在历史上,对于《提纲》的写作时间和目的以及如何对客观解释《提纲》的思想曾长期存在争议。马克思的这册笔记本里的内容虽...
    行走的师兄阅读 1,432评论 5 23