为什么这些常量以不同的方式声明?

时间:2017-02-15 02:35:25

标签: c enums embedded usb

以下是声明大量常量的代码:

#define TD_DATA_PID               0  /* control pipe state for td_sie_done */
#define TD_STATUS_PID             2  /* control pipe state for td_sie_done */
#define TD_DONE_PID               4  /* control pipe state for td_sie_done */
#define TD_DELETE_PID           0xee /* pipe command for td_sie_done */
#define TD_SETUP_PID            0xd  /* also used for control pipe state for tf_sie_done */
#define TD_IN_PID               0x9
#define TD_OUT_PID              0x1
#define TD_SOF_PID              0x5
#define TD_PREAMBLE_PID         0xc
#define TD_NAK_PID              0xa
#define TD_STALL_PID            0xe
#define TD_DATA0_PID            0x3
#define TD_DATA1_PID            0xb

#define TD_NORMAL_TIMEOUT         0
#define TD_LONG_TIMEOUT           1

enum TD_CTRL_BITS
{
    TD_CTRL_ARM         = 0x01,
    TD_CTRL_ISOCH       = 0x10,
    TD_CTRL_SYNC_SOF    = 0x20,
    TD_CTRL_DTOGGLE     = 0x40,
    TD_CTRL_PREAMBLE    = 0x80
};


enum TD_STATUS_BITS
{
    TD_STATUS_ACK       = 0x01,
    TD_STATUS_ERROR     = 0x02,
    TD_STATUS_TIMEOUT   = 0x04,
    TD_STATUS_SEQ       = 0x08,
    TD_STATUS_SETUP     = 0x10,
    TD_STATUS_OVERFLOW  = 0x20,
    TD_STATUS_NAK       = 0x40,
    TD_STATUS_STALL     = 0x80
};

此代码适用于USB OTG控制器,低级嵌入式系统,因此使用C而不是C ++。

所有#define值都用于为单个一个字节变量赋值。变量是数据包ID,因此所有#define常量都以_PID结尾。这很清楚。

然而,枚举部分尚不清楚。

基本上,有一个单字节长的寄存器包含TD_CONTROL_BITS,可能想要读/写它。每个位都会影响硬件的行为。给予枚举内每个常量的值是对应于该函数的位,例如ARM位是位0,同步传输位是位2 e.t.c.该寄存器被读取并写入。

类似也适用于TD_STATUS_BITS。字节长状态寄存器中的每个位提供有关传输状态的独立信息。因此,常数TD_STATUS_ACK,TD_STATUS_ERROR e.t.c可以用作位掩码来读取状态寄存器。该寄存器仅在外部硬件更新时回读,并且只写入0值才能复位。

现在我的问题是,为什么不将#define常量用于一切?在这种情况下,枚举会带来什么好处?此外,在TD_CTRL_BITS的情况下,我们可能想要逻辑OR位一起,例如TD_CTRL_ARM | TD_CTRL_DTOGGLE。我不认为枚举允许这样做。

2 个答案:

答案 0 :(得分:2)

这是一个非常标准的编码惯例,你会在职业生涯中多次看到它。

使用枚举是一种关联一组标志的方法,这样你就可以告诉它们属于一起。从代码中可以清楚地看出哪些是控制标志,哪些是状态标志,因为它在枚举标签中是这样说的。有些语言甚至强制执行标志类型,以便只有控制标志可以与控制参数一起使用,并且可以与状态标志一起使用。 C#这样做,我不认为C会这样做,C ++可能。

更可读的代码是一件好事,因为它意味着更少的错误和更少的维护开销

答案 1 :(得分:2)

在这种情况下没有明显的区别。您可以使用枚举常量或定义,在这种特定情况下没有明显的对或错。

总的来说,枚举常量对于与硬件相关的编程是危险的,因为它们必须始终是int类型,它始终是有符号的。在执行按位算术等操作时,应避免使用带符号的操作数。

您定义的整数常量(例如0xee)也存在同样的问题 - 它们也是带符号的int类型。但是对于定义,您可以选择后缀整数常量,例如0xeeu。现在你得到unsigned int,这样更方便,更安全。

签名int常量的一些示例会导致错误:

  • if(~TD_CTRL_ARM > 0)错误,操作数已变为否定。
  • TD_STATUS_STALL << 24错误,假设32位为int。代码将数据移入int的符号位,调用未定义的行为。
  • ~TD_CTRL_ARM >> n错误,您可能会以算术移位或逻辑移位结束。代码是不可移植的,可能会以意想不到的方式运行。
  • uint8_t data; ... if((~TD_CTRL_ARM & data) > 0)。 Bug,签名的左操作数意味着隐式提升的data将保持签名类型。即使右操作数被明确声明为unsigned,结果也可能会变为负数。如果左操作数是unsigned int,则右操作数也将变为无符号,代码将按预期工作。

等等。

建议/良好实践:

  • 如果数学等明确需要,则仅在嵌入式系统中使用签名类型。
  • 切勿将enum用于任何形式的位表示或按位操作。
  • 始终使用U / u后缀整数常量。
  • 明确说明类型大小和签名:尽可能使用stdint.h
  • 在表达式中使用小整数类型时要非常小心。 unsigned charboolunsigned shortuint8_t等。由于整数提升,他们可能会更改签名。
  • 实际上学习并理解隐式类型促销在C中是如何工作的。可怕数量的C程序员不知道这一点。在分配工作期间,所有C程序员都应该知道整数提升通常的算术转换(平衡)和类型转换。如果你不了解这些,你重复写出微妙且经常严重的错误。
  • 采用专业编码标准,这是C的安全子集,如MISRA-C,并使用静态代码分析。然后你就可以免费获得以上所有内容。