C/C++中define定义的常量与const常量

常量是在程序中不能更改的量,在C/C++中有两种方式定义常量,一种是利用define宏定义的方式,一种是C++中新提出来的const型常变量,下面主要讨论它们之间的相关问题

define定义的常量:

  define是预处理指令的一种,它用来定义宏,宏只是一个简单的替换,将宏变量所对应的值替换,如下面的代码:

1
2
3
4
5
#define NUM 2
int main()
{
printf("%d", NUM);
}

编译器在编译时处理的并不是这样的代码,编译器会首先处理预处理指令,根据预处理指令生成相关的代码文件,然后编译这个文件,得到相关的.obj文件,最后通过链接相关的.obj文件得到一个可执行文件,最典型的是我们一般在.cpp文件中写的#include指令,在处理时首先将所需包含的头文件整个拷贝到这个.cpp文件中,并替换这个#include指令,然后再编译生成的文件,这个中间文件在Windows中后缀为.i,在Visual C++ 6.0中以此点击Project–>Settings–>C/C++,在Project Options最后一行加上’/P’(P为大写)这样在点击编译按钮时不会编译生成obj文件,只会生成.i文件,通过这个.i文件可以看到在做预处理的时候会将 NUM替换成2然后在做编译处理,这个时候点击生成时会出错,因为我们将编译选项修改后没有生成.obj文件但是在生成时需要这个文件,因此会报错,所以在生成时要去掉这个/P选项。而我们看到在使用const 定义的时候并没有这个替换的操作,与使用正常的变量无异。

const 型变量

从本质上将const型变量仍然是一个变量,仍然占内存空间,它只是在语法层面上限定这个变量的值不可以修改,我们可以通过强制类型转化或者通过内嵌汇编的形式修改这个变量的值,比如下面的代码:

1
2
3
4
5
6
7
int main(int argc, char* argv[])
{
const nNum = 10;
int *pNum = (int*)&nNum;
printf("%d\n", nNum);
return 0;
}

编译器在编译时处理的并不是这样的代码,编译器会首先处理预处理指令,根据预处理指令生成相关的代码文件,然后编译这个文件,得到相关的.obj文件,最后通过链接相关的.obj文件得到一个可执行文件,最典型的是我们一般在.cpp文件中写的#include指令,在处理时首先将所需包含的头文件整个拷贝到这个.cpp文件中,并替换这个#include指令,然后再编译生成的文件,这个中间文件在Windows中后缀为.i,在Visual C++ 6.0中以此点击Project–>Settings–>C/C++,在Project Options最后一行加上’/P’(P为大写)这样在点击编译按钮时不会编译生成obj文件,只会生成.i文件,通过这个.i文件可以看到在做预处理的时候会将 NUM替换成2然后在做编译处理,这个时候点击生成时会出错,因为我们将编译选项修改后没有生成.obj文件但是在生成时需要这个文件,因此会报错,所以在生成时要去掉这个/P选项。而我们看到在使用const 定义的时候并没有这个替换的操作,与使用正常的变量无异。const型变量只是在语法层面上限定这个变量的值不可以修改,我们可以通过强制类型转化或者通过内嵌汇编的形式修改这个变量的值,比如下面的代码:

1
2
3
4
5
6
7
int main(int argc, char* argv[])
{
const nNum = 10;
int *pNum = (int*)&nNum;
printf("%d\n", nNum);
return 0;
}

1
2
3
4
5
6
7
const nNum = 10;
__asm
{
mov [ebp - 4], 10
}
printf("%d\n", nNum);
return 0;

但是我们看到,这两种方式修改后,输出的值仍然是10,这个原因我们可以通过查看反汇编代码查看

1
2
3
4
5
;printf("%d\n", nNum);
00401036 push 0Ah
00401038 push offset string "%d\n" (0042001c)
0040103D call printf (00401070)
00401042 add esp,8

在调用printf的时候,入栈的参数是10,根本没有取nNum值得相关操作,在利用const定义的常量时,编译器认为既然这是一个常量,应该不会修改,为了提升效率,在使用时并不会去对应的内存中寻址,而是直接将它替换为初始化时的值,为了防止这种事情的发生,可以利用C++中的关键字:volatile。这个关键字保证每次在使用变量时都去内存中读取。

总结

我们可以总结出const和define的几个不同之处:

  1. define是一个预处理指令,const是一个关键字。

  2. define定义的常量编译器不会进行任何检查,const定义的常量编译器会进行类型检查,相对来说比define更安全

  3. define的宏在使用时是替换不占内存,而const则是一个变量,占内存空间

  4. define定义的宏在代码段中不可寻址,const定义的常量是可以寻址的,在数据段或者栈段中。

  5. define定义的宏在编译前的预处理操作时进行替换,而const定义变量是在编译时决定

  6. define定义的宏是真实的常量,不会被修改,const定义的实际上是一个变量,可以通过相关的手段进行修改。