预处理--》编译--》运行的区别

相信很多人懂这个问题,也很多人没想过,包括我,今天看书想到了就写下来。先看程序(抱歉在linux下没有找到舒服的可以复制terminal的工具,只好截图了,将就着看看)

注释的就先不看了,看那几行没有注释的enum coordinate_type 表示一个枚举(Enumeration)类型。枚举类型的成员是常量,它们的值由编译器自动分配,例如定义了上面的枚举类型之后,RECTANGULAR就表示常量0,POLAR 表示常量1。如果不希望从0开始分配,可以这样定义:enum coordinate_type { RECTANGULAR = 1, POLAR };

这样,RECTANGULAR就表示常量1,而POLAR 表示常量2。枚举常量也是一种整型,其值在编译时确定,因此也可以出现在常量表达式中,可以用于初始化全局变量或者作为case 分支的判断条件。注意:枚举常量是不占用内存的,它们在编译时被全部求值,只有定义了enum 变量才会占用内存。

有一点需要注意,虽然结构体的成员名和变量名不在同一命名空间中,但枚举的成员名却和变量名在同一命名空间中,所以会出现命名冲突。那什么是命名空间呢?我的理解是在运行程序时会为每一个函数开辟一个函数帧栈,局部变量之类的可以在这里赋值运算等,如果在这个函数帧栈里同个等级里(指的是不再加{}构成语句块)同样的命名会造成冲突的那就属于同个命名空间,如上所述,结构体的成员名跟某个变量名命令重复是不会冲突的,而枚举类型成员名跟某个变量名重复是会造成冲突的,如编译时会提示错误如下:

那如果加了{}呢,如:

再次编译,提示就不一样了:

这时就不会提示发生冲突,大家都知道如果函数内的局部变量跟全局变量重名,则在函数内全局变量被屏蔽了,这里也是同样的道理,就是在函数内{}语句块也屏蔽了外围的,里所应当的是函数的局部变量等函数调用完后存储空间就会释放,而{}里面更快释放,可以看到打印完之后里面的rectanger变量就会被释放,但polar变量得等整个函数调用完毕才会释放,因为这里使用的是枚举类型中的成员。

那这里提示警告,是否能运行呢?当然了,因为只要不出现错误只出现警告是可以生成可执行文件的,只是有警告就意味着程序有bug,是很危险的。这里的意思是因为局部变量rectanger没有初始化,所以运行打印时会是不确定的值,即每次运行都可能是不一样的结果,要记住:局部变量是函数调用时才赋值的!局部变量存储空间地址也许会随着每次函数调用时而不同,如果你设定了初值,那空间怎么变里面的值都是你赋予的那个,但如果没有初始化,那每次运行都是不确定的值。如下图:

下面看把枚举类型写在函数外面的情况:

编译一下,看看出现什么提示:

可以看到没有发生命名冲突,只是还是提示没有初始化的问题,因为在这里的枚举常量是全局的,不会跟局部变量命名冲突,但是会被覆盖掉。

如果前面加前缀const如 const int A;  表明是只读的,注意,像A这种const 变量在定义时必须初始化如const int A = 100;。因为只有初始化时才有机会给它一个值,对于全局来说一旦定义之后就不能再改写了,也就是不能再赋值了,编译通过但运行时会出现段错误。而如果A 是局部变量则可以通过 int *p = &A;  *p = 200;来改写,编译通过且可以运行。其实加了关键字const只是提示编译器这个变量是常量,如果我们在接下来的操作中试图更改它,编译器会报错,而并不是真正的常量,上面的例子也说明通过指针也是可以更改的,什么情况下完全不能修改呢,当A是加const限定且初始化的全局变量,此时A位于.rodata段

还有个特例就是:函数中的static变量不同于以前我们讲的局部变量,它并不是在调用函数时分配,在函数返回时释放,而是像全局变量一样静态分配,所以用“static”(静态)这个词。另一方面,函数中的static变量的作用域和以前讲的局部变量一样,只在函数中起作用。

全局变量的作用域从开始定义的地方到文件的末尾,在任何函数中都可以访问全局变量,整个程序运行完毕会释放全局变量的存储空间,当然同时还有代码的存储空间也会被释放,而局部变量的存储空间早在他们之前释放; 如果全局变量没有赋予初值,则初始化为0,位于.bss段。

如果全局变量前面加个前缀static则表示此变量是local的而不是global的,意思是不能被其他文件所调用。

下面看预处理:

看看编译会提示什么:

很明显就是因为宏定义了rectanger,如果有重名的话,宏定义覆盖所有其它标识符,因为它在预处理阶段而不是 编译阶段处理,所以在函数里面重新定义rectanger会提示表明这个变量名已经是个常量了。 我们可以使用 gcc -E来查看预处理后而编译前的东西,一看这么多页屏幕都看不完整加个less查看,居然有好几屏幕,只截取最后面的一部分来看:

是不是发现了啊,预处理的时候已经把rectanger 都替换成宏定义中的 1了,所以接下去进行编译时当然会报错了,因为你在 int 1啊,能不错吗?那前面输出的是什么东西呢,其实就是将一些头函数进行展开。 反正处理的步骤就是 预处理 --》 编译 --》 运行,但步骤的不同是涉及到很多东西的,比如全局变量和局部变量的赋值,为什么全局变量只能用常量来初始化而局部变量可以用带数学函数的表达式来初始化呢?如double pi = acos(-1.0); 因为程序开始运行时要用适当的值来初始化全局变量,所以初始值必须保存在编译生成的可执行文件中,因此初始值在编译时就要计算出来,然而上面那种Initializer的值必须在程序运行时调用 acos函数才能得到,所以不能用来初始化全局变量。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小古哥的博客园

JavaScript闭包的深入理解

闭包算是javascript中一个比较难理解的概念,想要深入理解闭包的原理,首先需要搞清楚其他几个概念: 一、栈内存和堆内存 学过C/C++的同学可能知道,计算...

3517
来自专栏V站

PHP反序列化深入理解

在PHP中右serialize()和unserialize()两个函数,php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。u...

1315
来自专栏炉边夜话

java 异常处理学习笔记

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

612
来自专栏机器学习从入门到成神

C++指针与引用的区别

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_35512245/articl...

1111
来自专栏Java帮帮-微信公众号-技术文章全总结

Java读写Properties属性文件公用方法

Java中有个比较重要的类Properties(Java.util.Properties),主要用于读取Java的配置文件,各种语言都有自己所支持的配置文件,配...

912
来自专栏LanceToBigData

异常处理升级版

其实前面就写了一篇异常处理的文章,但是那个文章实在是感觉太详细了,不太好复习。所以今天我就再写一篇这样就更好复习了。 一、异常概述   在我们日常生活中,有时会...

1769
来自专栏web前端

Vuejs --02 Vue实例

一、构造器      1、vm(view model 表示Vue实例),每个Vuejs都是通过构造函数Vue创建Vue的根实例启动 var vm = new V...

2088
来自专栏从零开始学 Web 前端

01 - JavaSE之基础及面向对象

byte(-128 ~ 127) short(-32768 ~ 32767) int(-2147483648 ~ 2147483647)

1274
来自专栏博岩Java大讲堂

Java虚拟机--类加载机制

2767
来自专栏一个会写诗的程序员的博客

Kotlin 扩展函数 与 JS 的 prototypeKotlin 扩展函数 与 JS 的 prototype

Kotlin的扩展函数功能使得我们可以为现有的类添加新的函数,实现某一具体功能 。 扩展函数是静态解析的,并未对原类添加函数或属性,对类本身没有任何影响。 ...

482

扫码关注云+社区