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

相信很多人懂这个问题,也很多人没想过,包括我,今天看书想到了就写下来。先看程序(抱歉在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 条评论
登录 后参与评论

相关文章

来自专栏ml

C与C++在const用法上的区别

       首先,C和C++在大体结构上不同,却在语法上相同。  所以在使用的时候,我们会时常遇到一些莫名其妙的问题,觉得语法上是正确的,但是编译的时候却出现...

2984
来自专栏Python入门

python超详细的基础笔记你学会了么

python是一种面向对象的解释型计算机程序设计语言,python的是吉多·范罗苏姆(Guido van Rossum)于1989年发明

902
来自专栏阿凯的Excel

Python读书笔记11(循环遍历所有内容)

前面的文章和大家分享了数字、字符串、列表和元组,我们重新声明这些变量回顾一下! ? 数字、浮点数直接用等号声明 字符串需要将内容用英文单引号或双引号括起来 列...

2798
来自专栏有趣的Python

2-Linux C语言指针与内存-学习笔记

为原来的变量值加上*, change函数改为传入&a &b 3和5可以成功的交换。

813
来自专栏Pulsar-V

GTK基础操作类

1 类型定义 整数类型:gint8、guint8、gint16、guint16、gint31、guint32、gint64、guint64。不是所有的平台都提...

2525
来自专栏郭耀华‘s Blog

剑指offer第二天

18.二叉树的镜像 操作给定的二叉树,将其变换为源二叉树的镜像。 ? /** public class TreeNode { int val = 0...

2586
来自专栏编程

Python读书笔记11

前面的文章和大家分享了数字、字符串、列表和元组,我们重新声明这些变量回顾一下! 数字、浮点数直接用等号声明 字符串需要将内容用英文单引号或双引号括起来 列表是外...

1778
来自专栏行者常至

01.python基础知识快速入门

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

洛谷P1313 计算系数【快速幂+dp】

P1313 计算系数 题目描述 给定一个多项式(by+ax)^k,请求出多项式展开后x^n*y^m 项的系数。 输入输出格式 输入格式: 输入文件名为facto...

1985
来自专栏向治洪

模板方法模式

概述 概念:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步骤。模板方法模式属于行为类模式。 模板...

1767

扫码关注云+社区