天天看點

常量和變量(你想知道的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)
 
轉載請注明出處