天天看点

常量和变量(你想知道的C语言 3.3)

Q: 常量和变量的本质区别是什么?

A: 常量是不可改变的"变量", 变量是可改变的"常量". 我中有你你中有我是世界的本质,常量和变量只是为了方便表达特定含义的代名词,它们都是一种实体存在。

    我们可能会关注常量和变量在编译器或者运行期表达方式的不同,常量的不可改变特性有优化和保护的可能。

Q: const int i = 1和int i = 1有何差异?

A: const是写给编译器看的, 为防止修改i的代码出现,而汇编代码不受const影响。

const int i = 1;
int i = 1;
           

   对应的汇编代码是一样的:

movl	$0x1, %esi
           

Q: const变量有何种方式可以修改?

A: 我们可以绕过普通的变量赋值手法, 找到变量的地址,用地址解引用的方式绕过编译器的"小脑袋瓜"。

/*
   Xi Chen([email protected])
   cxsjabcabc
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
	const int i = 1;
	int *p;

	// modify i = 2
	p = &i;
	*p = 2;

	printf("%d %d\n", i, *p);

	return 0;
}
           

  运行结果: 

1 2
           

  虽然我们透过i的地址修改了i的数值, 但是输出i还是最初的数值。这不难理解,编译器早知道i的常量,不管后面怎么间接修改i, printf方式输出i都只会拿最初的数值,但*p就不一样了。

pushq	%rbp
movq	%rsp, %rbp
leaq	0x35(%rip), %rdi
movl	$0x1, %esi   // 直接拿常量1
movl	$0x2, %edx   // 直接拿常量2
xorl	%eax, %eax
callq	0x100000f8e
xorl	%eax, %eax
popq	%rbp
retq
           

Q: 既然编译器知道const变量不能被修改, 为何不建立一个特别的区域保护const变量避免间接修改呢?

A: 程序区段布局模型决定了如上的代码没办法保护,位于堆栈区域本身可读写,如下放入全局区域才有机会做到。

/*
   Xi Chen([email protected])
   cxsjabcabc
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

const int i = 1;

int main(int argc, char *argv[])
{
	int *p;

	// modify i = 2
	p = &i;
	*p = 2;

	printf("%d %d\n", i, *p);

	return 0;
}
           

  运行:  

Bus error: 10
           

  i位于全局区域,而且是只读区域,代码*p = 2修改全局只读区域将被操作系统挡掉。

常量和变量(你想知道的C语言 3.3)

  0x3e(%rip)即是全局只读i的地址: 

常量和变量(你想知道的C语言 3.3)
作者:     陈曦
环境:     MacOS 10.14.5 (Intel i5)
         Apple LLVM version 10.0.1 (clang-1001.0.46.4)
         Target: x86_64-apple-darwin18.6.0
 
         Linux 3.16.83 (Ubuntu)
 
转载请注明出处