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

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

define定义的常量:

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

#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型变量只是在语法层面上限定这个变量的值不可以修改,我们可以通过强制类型转化或者通过内嵌汇编的形式修改这个变量的值,比如下面的代码:

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型变量只是在语法层面上限定这个变量的值不可以修改,我们可以通过强制类型转化或者通过内嵌汇编的形式修改这个变量的值,比如下面的代码:

int main(int argc, char* argv[])
{
    const nNum = 10;
    int *pNum = (int*)&nNum;
    printf("%d\n", nNum);
    return 0;
}
 const nNum = 10;
    __asm
    {
        mov [ebp - 4], 10
    }
    printf("%d\n", nNum);
    return 0;

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

;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定义的实际上是一个变量,可以通过相关的手段进行修改。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java架构

Java多线程编程——锁优化

1814
来自专栏JMCui

Git 中 .gitignore 的配置语法

    在日常的开发中,当我们需要将一个项目提交到 Git 时,并不是所有的文件都需要提交,比如一些自动生成的文件,类似于 .idea 文件、class 文件等...

2693
来自专栏自动化测试实战

flask第十八篇——模板【2】

2306
来自专栏小樱的经验随笔

【python进阶】深入理解系统进程2

2714
来自专栏GreenLeaves

四、CLR执行程序集中代码和IL代码简介

三、加载公共语言运行时中介绍了在安装了.Net Framework中加载公共语言运行时,公共语言运行时加载程序集的过程.以及通过vs stdio设置源码编译的目...

3068
来自专栏博岩Java大讲堂

Java虚拟机--类加载器如何加载一个Class文件

1795
来自专栏大内老A

ASP.NET Core管道深度剖析(4):管道是如何建立起来的?

在《管道是如何处理HTTP请求的?》中,我们对ASP.NET Core的请求处理管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管...

1916
来自专栏抠抠空间

Flask路由系统与模板系统

Flask中自定义模板方法的方式和Bottle相似,创建一个函数并通过参数的形式传入render_template,如:

1012
来自专栏蓝天

SHELL参数介绍

$0 = shell名称或shell脚本名称 $1 = 第一个shell参数 ... $9 = 第九个shell参数 $# = 位置参数的个数 "$*" = "...

834
来自专栏编程

Python模块知识8:configparser、压缩模块

一、configparser模块 configparser用于处理特定格式的文件,其本质上是利用open来操作文件。 文件格式如: ? 1.基本的读取配置文件 ...

1896

扫码关注云+社区

领取腾讯云代金券