C:enum vs constant vs define中的模式设置

时间:2017-09-22 13:03:06

标签: c enums embedded

我已经阅读了一些关于这个主题的问题:

我知道在#define宏上通常首选枚举,以获得更好的封装和/或可读性。此外,它允许编译器检查类型以防止出现一些错误。

const声明介于两者之间,允许进行类型检查和封装,但更加混乱。

现在我在内存空间非常有限的嵌入式应用程序中工作(我们经常需要争取字节保存)。我的第一个想法是常量比枚举占用更多的内存。但我意识到我不确定最终固件中会出现常数。

示例:

enum { standby, starting, active, stoping } state;

问题

在资源有限的环境中,enum vs #define vs static const在执行速度和内存印记方面有何比较?

4 个答案:

答案 0 :(得分:4)

为了尝试获得一些重要的元素,我做了一个简单的测试。

代码

我写了一个简单的C程序 main.c

#include <stdio.h>

#include "constants.h"

// Define states
#define STATE_STANDBY 0
#define STATE_START   1
#define STATE_RUN     2
#define STATE_STOP    3

// Common code
void wait(unsigned int n)
{
  unsigned long int vLoop;

  for ( vLoop=0 ; vLoop<n*LOOP_SIZE ; ++vLoop )
  {
    if ( (vLoop % LOOP_SIZE) == 0 ) printf(".");
  }
  printf("\n");
}

int main ( int argc, char *argv[] )
{
  int state = 0;
  int loop_state;

  for ( loop_state=0 ; loop_state<MACHINE_LOOP ; ++loop_state)
  {
    if ( state == STATE_STANDBY )
    {
      printf("STANDBY ");
      wait(10);
      state = STATE_START;
    }
    else if ( state == STATE_START )
    {
      printf("START ");
      wait(20);
      state = STATE_RUN;
    }
    else if ( state == STATE_RUN )
    {
      printf("RUN ");
      wait(30);
      state = STATE_STOP;
    }
    else // ( state == STATE_STOP )
    {
      printf("STOP ");
      wait(20);
      state = STATE_STANDBY;
    }
  }

  return 0;
}

constants.h 包含

#define LOOP_SIZE     10000000
#define MACHINE_LOOP  100

我考虑了三种变体来定义状态常量。如上所述的宏,枚举:

enum {
  STATE_STANDBY=0,
  STATE_START,
  STATE_RUN,
  STATE_STOP
} possible_states;

const

static const int  STATE_STANDBY = 0;
static const int  STATE_START   = 1;
static const int  STATE_RUN     = 2;
static const int  STATE_STOP    = 3;

,其余代码保持相同。

测试和结果

在64位Linux机器上进行测试,并使用gcc

进行编译

全球规模

  • gcc main.c -o main给出了

    宏:7310字节
    枚举:7349字节
    const:7501字节

  • gcc -O2 main.c -o main给出了

    宏:7262字节
    枚举:7301字节
    const:7262字节

  • gcc -Os main.c -o main给出了

    宏:7198字节
    枚举:7237字节
    const:7198字节

启用优化后,const和宏变体的大小相同。枚举总是略大。使用gcc -S我可以看到.comm中的差异是possible_states,4,4。所以枚举总是大于宏。 const可以更大,但也可以优化。

截面尺寸

我使用objdump -h main检查了程序的几个部分:.text,.data,.rodata,.bss,.dynamic。在所有情况下,.bss有8个字节,.data,16个字节和.dynamic:480个字节。

.rodata有31个字节,但非优化的const版本(47字节)除外。

.text从620字节到780字节,具体取决于优化。未被优化的const是唯一一个与同一旗帜不同的人。

执行速度

我运行了几次程序,但我没有发现不同版本之间存在实质性差异。没有优化,它运行大约50秒。 -O2最多20秒,-Os最多3分钟。我用/usr/bin/time测量了时间。

RAM使用

使用time -f %M,我在每种情况下得到大约450k,而在使用valgrind --tool=massif --pages-as-heap=yes时,我得到6242304。

结论

每当激活一些优化时,唯一值得注意的差异是枚举情况大约多40字节。但没有RAM或速度差异。

还有关于范围,可读性......个人偏好的其他论点。

答案 1 :(得分:1)

  

我知道在#define宏上通常首选枚举,以获得更好的封装和/或可读性

首选枚举主要是为了提高可读性,但也因为它们可以在本地范围内声明,并且它们增加了更多的类型安全性(特别是在使用静态分析工具时)。

  

常量声明介于两者之间,允许进行类型检查和封装,但更麻烦。

不是,它取决于范围。 &#34;全球&#34; const可能很混乱,但它们并不像全局读/写变量那样糟糕,在某些情况下可以证明是合理的。 const优于其他形式的一个主要优点是,这些变量倾向于在.rodata中分配,您可以使用调试器查看它们,这些内容对于宏和枚举来说是不可能的(取决于调试器的好坏。)

请注意,#define始终是全局的,而enum可能也可能不是。{/ p>

  

我的第一个想法是常量占用的内存比枚举更多

这是不正确的。 enum变量通常是int类型,但它们可以是较小的类型(因为它们的大小可能会有所不同,但它们对于可移植性不利)。然而,枚举常量(即枚举声明中的内容)总是 int,这是一种至少16位的类型。

另一方面,const与您声明的类型完全一样大。因此,如果您需要节省内存,const优先于enum

  

在资源有限的环境中,enum vs #define vs static const在执行速度和内存印记方面有何比较?

执行速度可能没有差别 - 因为它是如此系统特定所以不可能说。但是,由于枚举值往往会提供16位或更大的值,因此当您需要节省内存时,它们是个坏主意。如果您需要精确的内存布局,那么它们也是一个坏主意,就像在嵌入式系统中一样。然而,编译器当然可以将它们优化为更小的尺寸。

其他建议:

  • 始终在嵌入式系统中使用stdint.h类型,尤其是在需要精确内存布局时。
  • 枚举很好,除非你需要它们作为某种内存布局的一部分,比如数据协议。不要在这种情况下使用枚举。
  • 当您需要将某些内容存储在闪存中时,
  • const是理想的选择。这些变量有自己的地址,更容易调试。
  • 在嵌入式系统中,优化代码以减少闪存大小通常没有多大意义,而是优化以减小RAM大小。 #define将始终以.text闪存结尾,而枚举和常量可能会在RAM中.rodata闪存或.text闪存结束。
  • 在嵌入式系统上优化大小(RAM或闪存)时,在地图文件(链接器输出文件)中跟踪变量并查找突出显示的内容,而不是四处奔波并手动优化随机内容一时兴起。通过这种方式,您还可以检测到某些应该const的变量是否已经错误地结束在RAM中(一个错误)。

答案 2 :(得分:1)

枚举,#define和static const通常会给出完全相同的代码(因此具有相同的速度和大小),具有某些假设。

声明枚举类型和枚举常量时,这些只是整数常量的名称。他们没有任何空间或时间。这同样适用于#define&#39; d值(尽管这些不限于int&#39; s。)

A&#34;静态const&#34;声明可能占用空间,通常在flash中的只读部分。通常情况下,只有在未启用优化时才会出现这种情况,但如果您忘记使用&#34;静态&#34;并写一个简单的&#34; const&#34;,或者如果你取静态const对象的地址。

对于类似给定示例的代码,结果将与所有版本相同,只要至少启用基本优化(以便优化静态const对象)。但是,样本中存在错误。这段代码:

enum {
  STATE_STANDBY = 0,
  STATE_START,
  STATE_RUN,
  STATE_STOP
} possible_states;

不仅创建枚举常量(不占空间)和匿名枚举类型,还创建一个名为&#34; possible_states&#34;的该类型的对象。这具有全局链接,并且必须由编译器创建,因为其他模块可以引用它 - 它被放在&#34; .comm&#34;共同部分。应该写的是其中之一:

// Define just the enumeration constants
enum { STATE_STANDBY, ... };

// Define the enumeration constants and the
// type "enum possible_states"
enum possible_states { STATE_STANDBY, ... };

// Define the enumeration constants and the
// type "possible_states"
typedef enum { STATE_STANDBY, ... } possible_states;

所有这些都将提供最佳的代码生成。

在比较编译器生成的大小时,请注意不要包含调试信息!给出的示例显示了枚举版本的更大的目标文件,部分原因是上面的错误,但主要是因为它是一种新类型并导致更多的调试信息。

这三种方法都适用于常数,但它们都有其特殊性。 A&#34;静态const&#34;可以是任何类型,但您不能将其用于案例标签或数组大小等内容,也不能用于初始化其他对象。枚举常量仅限于类型&#34; int&#34;。 #define宏不包含类型信息。

但是,对于这种特殊情况,枚举类型有一些很大的优点。它在一个定义中收集状态,这个定义更清晰,它允许你使它们成为一种类型(一旦你得到正确的语法:-))。当您使用调试器时,您应该看到枚举类型变量的实际枚举常量,而不仅仅是数字。 (&#34; state&#34;应声明此类型。)并且您可以从工具中获得更好的静态错误检查。而不是使用一系列&#34; if&#34; /&#34;否则如果&#34;语句,使用开关并使用&#34; -Wswitch&#34; gcc中的警告(或其他编译器的等效警告),如果您忘记了案件,则会发出警告。

答案 3 :(得分:0)

我使用了一个OpenGL库,它定义了枚举中的大多数常量,默认的OpenGL-header将其定义为#defines。因此,作为标题/库的用户,没有太大的区别。这只是设计的一个方面。使用普通C时,没有static const,直到它发生变化或者像(extern) static const char[] version;这样的大字符串。对于我自己,我避免使用宏,因此在特殊情况下,标识符可以重复使用。当将C代码移植到C ++时,枚举甚至是作用域并且是Typechecked。

看点:characterusage&amp;可读性:

#define MY_CONST1 10
#define MY_CONST2 20
#define MY_CONST3 30
//...
#define MY_CONSTN N0

VS

enum MyConsts {
    MY_CONST1 = 10,
    MY_CONST2 = 20,
    MY_CONST3 = 30,
    //...
    MY_CONSTN = N0 // this gives already an error, which does not happen with (unused) #defines, because N0 is an identifier, which is probably not defined
};