搜索
bottom↓
回复: 29

请教枚举与#define预处理有何不同

[复制链接]

出0入0汤圆

发表于 2013-4-18 09:01:43 | 显示全部楼层 |阅读模式
#define的内容在编译器预处理时就会进行替换。
但枚举的替换过程又是怎么样的,它们之间有何不同?

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2013-4-18 09:07:03 | 显示全部楼层
define 是预处理时替换
枚举是编译的时候替换,编译器对枚举进行检测

出0入0汤圆

发表于 2013-4-18 09:07:33 | 显示全部楼层
宏是预处理期操作,基本等价于纯粹的文字游戏。类型等等不在检查之列。
枚举是编译期操作,是带有类型归属和类型检查的的操作。

出0入8汤圆

发表于 2013-4-18 09:11:11 | 显示全部楼层
一个是预处理。编译前替换。
另一个是标准语法。

出0入0汤圆

发表于 2013-4-18 09:13:28 | 显示全部楼层
本帖最后由 monkerman 于 2013-4-19 09:41 编辑
枚举常量与宏的区别主要有几点:

1. 枚举常量是实体中的一种,但宏不是实体;
2. 枚举常量属于常量,但宏不是常量(有些书把类对象宏称为预处理常量,这是错误的说法,标准C/C++没有预处理常量这种不知从哪里冒出来的说法);
3. 枚举常量具有类型,但宏没有类型,枚举变量具有与普通变量相同的诸如作用域、值等性质,但宏没有;

枚举类型主要用于限制性输入,例如,某个函数的某参数只接受某种类型中的有限个数值,除此之外的其它数值都不接受,这时候枚举能很好地解决这个问题.

                                                                                                                                                                                                       --------------飞天御剑流

注: 这个枚举常量指的是成员. 枚举变量是指枚举集合.

出0入0汤圆

 楼主| 发表于 2013-4-18 09:59:12 | 显示全部楼层
本帖最后由 guew 于 2013-4-18 10:02 编辑

诸位,“define 是预处理时替换”,也就是说尽管做了n个#define,但如果在下面的编码中只引用了一个#define的内容,那么实际存在flash中的常量只有那个被引用了的。
如:
#define a 0
#define b 1
#define c 2
void mian()
{
    int x = a;//由于是预处理,只有a的值被存在flash里,并被赋值了,其余没用到的被无视。
}

“枚举是编译的时候替换,编译器对枚举进行检测”,在main()的前面写了个enum,并在main()里赋值,那是不是没有被用的enum常量也会被存在flash里?
如:
enum this_enum
{
    a,
    b,
    c
}

void main()
{
    int x = a;//尽管只把a赋值给x,但b,c的值同样被存在flash里了?
}

这也就是很多单片机原厂头文件里多用#define,而没有用enum的原因?

出0入0汤圆

发表于 2013-4-18 21:45:52 | 显示全部楼层
guew 发表于 2013-4-18 09:59
诸位,“define 是预处理时替换”,也就是说尽管做了n个#define,但如果在下面的编码中只引用了一个#define ...

这个概念是混淆和糊涂的,根本没有说到点子上。这里有几个层面的基础知识,分析如下。
1、首先要明白什么是变量、什么是常量,他们有什么区别。
     变量和常量的本质区别是:在计算机系统的内存RAM中,变量是分配存储空间的;常量不会在RAM内存中分配存储空间的。不知道各位学过8086的微机原理吗?从汇编角度看如下的代码:

     var1 db 3          ===>var1是变量,在内存中分配空间,假定地址为1000h,因此意味在在内存地址1000H的单元中,存放一个数值3
     var2 equ 3        ===>var2是常量,相当C语言的#define var2 3,定义一个常量。VAR2在内存中没有分配空间。简单的讲,此时对于编译器讲,VAR2就是常数3

     进一步理解看下面两条c语言语句的区别,
        C语句     ==》为对应的汇编     --> 真正的机器汇编代码(预处理,编译过)
     B = var1    ==》 mov al,  var1    ---> mov al, [1000h]   ;   功能是把内存单元3000H处的值取出到AL寄存器中,属于直接寻址
     B = var2    ==》 mov al,  var2    ---> mov al, 3            ;    功能是把立即数3放入AL寄存器中,属于立即数寻址。
     可以清楚的看出,第2句的3,其实是在指令中的(也可以理解在flash中,但不全面。因为程序员也是可以在FLASH中定义变量的,只是这些变量的值不能更换,比如固定的7段码表,但它在flash中是有地址存储的,是通过地址读取的。而此句常量3,是指令的一部分,与变量属于完全不同的概念,不能理解它在flash中有专门的存储地址)

2、只有清楚的理解上面的概念,才能理解枚举与常量的不同。

2.1    枚举是一个变量,因此在存储器中必须给枚举变量分配空间地址。比如我们定义下面的枚举变量:

    enum {sun,mon,tue,wed,thu,fri,sat} workday;
    则表示,在内存中分配一个单元给枚举变量workday,这个同普通变量定义是相同的,但不同点是这个变量的赋值只能在0-6之间(此时sun代表常数0,mon代表常数1.....)

2.2
   正确的给枚举变量赋值应该是 workday = mon; workday = fri,但不能写成 workday = 1。这个规则是由编译器检查的。

2.3
    最后,在理解上面的概念后,我们可以知道, workday = mon 相当于 workday = 1 ,给变量赋值一个常数。只是由于枚举的限制,只能写成workday = mon.
    {sun,mon,tue,wed,thu,fri,sat}实际上也是定义和代表了常数0,1,2,3...6,只是这些常数定义只能给workday变量赋值和判断使用(编译器控制)
    既然sun,mon,tue,wed,thu,fri,sat是常数,在内存中也不会分配空间,一条C语言:
        C语句                 ==》 为对应的汇编     --> 真正的机器汇编代码(预处理,编译过)
       workday = mon    ==》 mov al,  mon     ---> mov al, 1            ;    功能是把立即数1放入AL寄存器中,属于立即数寻址。这里的1是指令的一部分了,与常量类似。

=========================================
不知道我解释的清楚吗?   

出70入0汤圆

发表于 2013-4-19 09:31:19 | 显示全部楼层
马老师讲得清晰明白,以前把枚举误认为是常量了。

出70入0汤圆

发表于 2013-4-19 09:36:07 | 显示全部楼层
monkerman 发表于 2013-4-18 09:13
...

枚举常量应该是枚举成员是常量。

出0入0汤圆

发表于 2013-4-19 09:42:33 | 显示全部楼层
绿茶山人 发表于 2013-4-19 09:36
枚举常量应该是枚举成员是常量。

已修改.

出0入296汤圆

发表于 2013-4-19 09:50:10 来自手机 | 显示全部楼层
顺便接着马老师的讲解说明下加了const修饰的变量并不是常量。const只是一个修饰限定,在语法上现定修饰的变量不应该被修改,但由于仍然分配了SRAM空间,所以仍然是变量。

出0入0汤圆

发表于 2013-4-19 10:26:00 | 显示全部楼层
Gorgon_Meducer 发表于 2013-4-19 09:50
顺便接着马老师的讲解说明下加了const修饰的变量并不是常量。const只是一个修饰限定,在语法上现定修饰的变 ...

哈哈哈,你中计了。

1、const的语义在C和C++上不一致。
2、const的变量是否分配内存在,C99 WG14/N1256,6.7.3-3的注释第114:
The implementation may place a const object that is not volatile in a read-only region of storage. Moreover, the implementation need not allocate storage for such an object if its address is neverused.

在C99里,加了const的变量定义性声明不一定导致内存分配。

出0入0汤圆

发表于 2013-4-19 10:33:29 | 显示全部楼层
machao 发表于 2013-4-18 21:45
这个概念是混淆和糊涂的,根本没有说到点子上。这里有几个层面的基础知识,分析如下。
1、首先要明白什么 ...

1 enum indentifier { enum_constant_1, enum_constant_2 }定义了枚举类型以及枚举常量。
  这两个声明性定义不会导致内存分配。
  在条目2.1中把定义一个匿名枚举类型,定义枚举常量以及定义一个枚举变量三件事情混为一谈。

2 C99标准6.7.2.2-4说:
  Each enumerated type shall be compatible with char,asigned integer type, or an unsigned integer type.
  如果枚举类型兼容相应的类型,编译器不一定会抛错误。

  enum 印象中 在很多编译器中按照整数类型进行处理的;有时候和整数类型混用不抛警告/错误。

实际上楼主位讨论的是宏,定义为常数;以及枚举常量的情形。这些都不会导致内存分配。
当值应用于某个变量的时候,是另外一个事情。

不是很严格,欢迎讨论。

出0入296汤圆

发表于 2013-4-19 11:25:58 | 显示全部楼层
dr2001 发表于 2013-4-19 10:26
哈哈哈,你中计了。

1、const的语义在C和C++上不一致。

哈哈,被吐槽了,看我反吐你。

1、const的语义在C和C++上不一致。
我默认只谈C

2、const的变量是否分配内存在,C99 WG14/N1256,6.7.3-3的注释第114:
The implementation may place a const object that is not volatile in a read-only region of storage. Moreover, the implementation need not allocate storage for such an object if its address is neverused.
这句话的意思前半段说明被const修饰以后可能会把这个对象放到一个read-only的存储器区域,实际还是分配了空间,至于这个read-only区域是不是read-only,这只是一个memory region的定义,可能真的是flash这样的地方,也可能是SRAM。但不管如何,都分配了空间。后半段实际上是说,如果这个地址从来没有被用到过,这个对象就可以不实际分配空间——这只是一个编译器的优化策略,很多编译器自己都会这么做的——这和const的本质实际上是无关的,因为即便是普通的静态变量,如果他的地址从来没有被用过,也是可以被优化掉的。

出0入0汤圆

发表于 2013-4-19 11:38:09 | 显示全部楼层
Gorgon_Meducer 发表于 2013-4-19 11:25
哈哈,被吐槽了,看我反吐你。

1、const的语义在C和C++上不一致。

在这里“may”不能解释为“可能”,而应解释为“可以”,与之相对的是“must”;换句话说是,编译器实现可以分配空间,也可以不分配空间;如果常量对象从没被以地址方式(比如指针)引用过的话,就可以不分配。

出0入0汤圆

发表于 2013-4-19 12:09:03 | 显示全部楼层
Gorgon_Meducer 发表于 2013-4-19 11:25
哈哈,被吐槽了,看我反吐你。

1、const的语义在C和C++上不一致。

我只是想说明:
C标准很多时候不规定实现,只约束在抽象机器上的行为。
对const来说,只要相关行为正确,实现是都可行的,即编译器不是必须为const类型的变量分配存储空间。C标准的附注实际上还是在注脚这个。
优化本身就是保证行为而改变实现么。

出0入0汤圆

发表于 2013-4-20 14:30:19 | 显示全部楼层
讨论这样的问题不能只从表面上理解,作为编译器可以有自己定义的规范(当然尽量保持与标准一致),但最根本的还是要回到汇编和机器代码这个最实际的层面上。

在机器语言的层面上就是只有常量和变量,变量需要分配存储空间,操作按地址实现(不管是在代码空间还是在数据空间),而常量就是指令的一部分(肯定在代码空间)。

    对于8086讲,代码空间和数据空间都在RAM中,所以对于高手讲,定义在代码空间的变量(哪怕是代码)在程序运行过程中也是可以改变的,而对于普通的单片机,代码空间是FLASH,所以通常在代码空间的变量就不能改变,但它本身与常量的概念是不同的。

   #define 定义的是常量已经没有疑问的。而const 定义的实际是一种变量(原则上其值不改变),或者称为“常数变量”,它需要分配存储空间的,在大家熟悉的keil中,const定义的变量就是放在ROM中的,通常其值是不改变的。

   现在许多新的支持IAP的单片机,程序运行过程中,代码空间也是可以修改的,那么原理分配在代码空间的变量值也是可以修改的。其实代码都可以动态的修改。了解和掌握到这个层次,可以设计出许多巧妙的功能,比如加密,防拷贝等。

    总之,不要仅仅局限在编译器层面上去理解这些概念,因为编译器没有通用的,针对不同的芯片,编译器会做相应的不同的定义和处理。最重要的是从机器结构和机器汇编这个层面上,理解最基本的道理。这样倒过来就可以理解编译器的定义了。

出0入17汤圆

发表于 2013-4-20 14:37:59 | 显示全部楼层
枚举比较方便;C编译器可以对其优化

#define预处理就是替换;


出0入17汤圆

发表于 2013-4-20 14:44:52 | 显示全部楼层
比如
enum weekday
    { sun,mon,tue,wed,thu,fri,sat } a,b,c;
    a=sun;
    b=mon;
    c=tue;


1;比写七个define简单;
2;如果写成a=10;编译器会提示

出0入0汤圆

发表于 2013-4-20 14:51:29 | 显示全部楼层
就是写A = 2 也不行,楼上在努力吧,还没真正理解。

出0入0汤圆

 楼主| 发表于 2013-4-20 16:32:53 | 显示全部楼层
本帖最后由 guew 于 2013-4-20 16:34 编辑
machao 发表于 2013-4-18 21:45
这个概念是混淆和糊涂的,根本没有说到点子上。这里有几个层面的基础知识,分析如下。
1、首先要明白什么 ...


马老师,可能我在发帖时,是想把枚举作为一个常数的容器来使用,方便同类数据的管理,来代替零散的#define。想了解的本意有两个:1. 两种方式对flash和ram消耗上的区别 2. 在程序真正跑起来后,效率熟高熟低
看了你的答复,我想了土方法来描述,试着绕过抽象的软件,你看看我说的在不在理?
________________________________________________________________
约定:代码空间在flash,数据空间在ram;指令长度,寄存器,ram宽度都是8位。

汇编代码                  机器码
mov al, [1000h] ——》8比特,8比特,16比特;//与代码有联系的地方有:32比特flash空间,8比特寄存器空间,8比特ram空间
mov al, 256       ——》8比特,8比特,16比特;//与代码有联系的地方有:32比特flash空间,8比特寄存器空间,没有ram空间








出0入0汤圆

 楼主| 发表于 2013-4-20 16:47:51 | 显示全部楼层
本帖最后由 guew 于 2013-4-20 16:49 编辑
machao 发表于 2013-4-20 14:30
讨论这样的问题不能只从表面上理解,作为编译器可以有自己定义的规范(当然尽量保持与标准一致),但最根本 ...


回头想想了,我用枚举的目的只是作为一个常量的容器来管理数据,来代替零散的#define,我的本意是想问这么个问题:
枚举在定义时,是否只是在是告诉编译器:我有一个枚举类型,如果在下面的代码里没有用到这个类型,就当我没说?
如:
enum my_num
{
    a,
    b,
    c
};

void main()
{
    uint8 x,y,z;
    x = a;
    y = b;
}

编译后,对应的汇编是不是就大概如下:
mov al 0;
mov al 1;

那个没有用到的c不会出现在汇编的任何地方?

出0入0汤圆

发表于 2013-4-20 22:16:47 | 显示全部楼层
guew 发表于 2013-4-20 16:47
回头想想了,我用枚举的目的只是作为一个常量的容器来管理数据,来代替零散的#define,我的本意是想问这 ...

你努力吧,概念还是不清楚。所有的编译器的东西都是虚的,硬件架构才是实的。你的硬件基础没有,发展空间没有,理解上就不能直接到达真正的实质。

出0入17汤圆

发表于 2013-4-21 13:52:38 | 显示全部楼层
machao 发表于 2013-4-20 14:51
就是写A = 2 也不行,楼上在努力吧,还没真正理解。

按照C的语法,枚举不指定初值的话是按0,1,2,3的顺序走的;
所以A=2 是有意义的,符合语法规范;

出0入0汤圆

发表于 2013-4-21 14:49:42 | 显示全部楼层
enum weekday { sun,mon,tue,wed,thu,fri,sat } a;
   定义一个枚举类型的变量A,其实A就是一个整形变量,那么为什么要定义成枚举类型呢?就是想在应用中限定该变量的值的范围,以及在程序编写中的方便和易懂。

因此在编程中规范的使用枚举变量以及赋值等应该如下:
     a= mon; 以及 if (a == mon)

当然,写成 a= 1;以及 if (a == 1) 也可以(在汇编上实际是等同的),但这就失去了定义和使用枚举的意义,定义一个简单的变量a不就完了吗,何必定义成枚举?
其实,就是写成a = 10; 编译也是通过的,只是给出一个警告。

实际上定义语句也是给编译器看的,严格的编译器会给出错误,但很多编译器放宽了限制和检查,此时通常给出一个警告提醒。许多编译器检查的级别是可以设定的。

因此,一旦你定义使用枚举,就应该按规范在程序中使用它,不要认为编译器放宽的限制和检查,采用非规范的方式。

楼上的 “A=2 是有意义的,符合语法规范”解释我是不认同的。什么是有意义的?A=2和A= 10都可以执行,也都符合语法规范,如何判断有意义无意义?
从程序员角度看枚举变量 A=TUE 是有意义的,表示该变量现在是星期二。而A = 2就是没有意义的,因为不知道2代表什么意思。尽管汇编指令的实质都是把2赋给了变量A。   

出0入0汤圆

 楼主| 发表于 2013-4-22 23:15:49 | 显示全部楼层
本帖最后由 guew 于 2013-4-22 23:19 编辑
machao 发表于 2013-4-20 22:16
你努力吧,概念还是不清楚。所有的编译器的东西都是虚的,硬件架构才是实的。你的硬件基础没有,发展空间 ...


翻看微机原理书,就像您说的,这个常量和变量的概念需要从寻址方式上来理解,常量是立即寻址,变量是储存器寻址。
话说,概念这东西没有感性的实践认识就容易忘。

大二学过 Microprocessors and Digital Systems,为了应付考试,在图书馆死背HC12的寄存器名称,哪些中断可屏蔽,哪些不可以。考试是过了,但一个寒假过去,记忆就模糊,又没有继续用实际例程来强化和深化对概念的理解,等毕业时就全忘光光了。

现在回头复习微机原理也是东墙漏补东墙式的投机学习,几次下了大决心来系统复习,最后一一不了了之

出0入296汤圆

发表于 2013-4-22 23:24:34 | 显示全部楼层
machao 发表于 2013-4-20 22:16
你努力吧,概念还是不清楚。所有的编译器的东西都是虚的,硬件架构才是实的。你的硬件基础没有,发展空间 ...

顶马老师这句话。这才是关键阿。编译器的东西都是虚的~都是人为附加上去的法则~隐藏在其下的要看
具体的芯片及其物理行为。如果要想保证代码的可移植性,则要在一定程度上遵守编译器建立的“虚假”规则
并能在一定程度上在少数需要的地方“凌驾于”这些规则。

出0入0汤圆

发表于 2013-5-12 11:07:15 | 显示全部楼层
好东西  学习 了

出0入0汤圆

 楼主| 发表于 2013-6-5 14:59:40 | 显示全部楼层
本帖最后由 guew 于 2013-6-5 15:02 编辑

看了一下汇编,CVAVR下,enum作为常量容器使用时,效果和#define一样,都作为指令的一部分,立即数寻址。
C程序:
  1. #define a 0
  2. enum this_enum
  3. {
  4.     b
  5. };

  6. void main(void)
  7. {
  8.     uint8 i,k;
  9.     i = a;
  10.     k = b;
  11.     while (1)            
  12.     {
  13.     }
  14. }
复制代码
汇编:
  1. @00000047: main
  2. ---- 1.c ------------------------------------------------------------------------------------------
  3. 35:           i = a;
  4. +00000047:   E010        LDI       R17,0x00       Load immediate
  5. 36:           k = a;
  6. +00000048:   E000        LDI       R16,0x00       Load immediate
  7. 38:           {
  8. +00000049:   CFFF        RJMP      PC-0x0000      Relative jump
复制代码

出0入0汤圆

发表于 2014-9-1 15:45:29 来自手机 | 显示全部楼层
还是不懂,#define和枚举,哪个更省RAM。比如,我定义了两组枚举,但只用到了其中一组枚举的一个成员,和我定义好几个#define只调用了一个,哪个更好
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 07:18

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表