【问题标题】:How can we use unions and structure within a #define statement我们如何在#define 语句中使用联合和结构
【发布时间】:2019-02-06 09:17:45
【问题描述】:

我正在浏览一个微控制器的头文件,当我发现这个时,

 #define DEFA(name, address) __no_init union \
{ \
  struct \
  { \
    volatile unsigned char  name##_L; \
    volatile unsigned char  name##_H; \
  }; \
  struct \
  { \
    volatile unsigned short name##L; \
    volatile unsigned short name##H; \
  }; \
  __ACCESS_20BIT_REG__ name; \
} @ address;

我有多个问题,

  1. 我不知道我们可以在#define 语句中使用联合和结构。编译器如何解释它们?
  2. “name##_L”和“name##L”是什么意思?,尤其是“##”。
  3. 什么是“__ACCESS_20BIT_REG__ 名称”?
  4. 这个新的“@”符号是什么,我用谷歌搜索了这个符号,我在任何地方都找不到与 @ 符号相关的东西,有人说这不是 C 标准,这是真的吗?是什么意思?

有人,请给我解释一下这段代码,一切都依赖于这段代码,一切都直接或间接地使用了这个#define。 :(

【问题讨论】:

  • "##" 是 C 预处理器的标记连接功能:en.wikipedia.org/wiki/C_preprocessor#Token_concatenation
  • 我不知道我们可以在#define 语句中使用联合和结构。编译器如何解释它们? - 您可以在预处理器符号定义中使用您想要的任何文本。预处理器所做的只是(相当)愚蠢的文本替换。
  • @Swordfish:宏替换替换预处理器标记,而不是文本,# 和 ## 运算符除外。
  • @EricPostpischil 到目前为止我看到的所有预处理器标记都是文本 ^^
  • @Swordfish:如果宏替换替换了文本,那么宏中的X-4 可能会变成浮点文字3e-4。这不可以。代币替换不能形成新的代币。初始词法分析已经执行,不再重复。

标签: c embedded c-preprocessor preprocessor


【解决方案1】:
  1. 您可以将大多数内容放在宏中,它们大多只是文本替换(尽管在逐个令牌级别上)。然而,将 typedef 和变量声明放在宏中是不好的做法,除非没有其他选择,否则应该避免。

  2. 它将令牌粘贴过去并创建一个新的预处理器令牌。在这种情况下,它会创建成员变量名称。如果您使用DEFA(foo,0),您将获得名为foo_Hfoo_L 的成员。

    值得注意的是,如果字段只命名为 H 和 L,这个特定的宏可能会更好。为什么有人要输入 reg.foo_H 而不是 foo.H?使用_LL 后缀来区分8 位和16 位访问也不是一个好主意。

  3. 同一代码使用的一些自定义类型。为什么他们在所谓的“20 位”和 32 位结构之间有一个联合,我不知道。

  4. 非标准扩展。通常它用于在特定地址分配变量,这里就是这种情况。可能您有一个内存映射的硬件寄存器,而这个宏应该是此类寄存器的通用声明。

同样值得注意的是,联合语法不是标准的 C。您需要命名联合。匿名结构是自 C11 以来的标准 C,但我怀疑这是来自现代编译器。

总的来说,非常糟糕的代码。您可以在芯片/工具供应商提供的微控制器寄存器映射中找到一种非常典型的垃圾代码。不符合 ISO C 或 MISRA-C。

【讨论】:

    【解决方案2】:
    1. 我不知道我们可以在#define 语句中使用联合和结构。编译器如何解释它们?

    Define 语句只是宏。它们不是由编译器而是由预处理器解释的。在宏中定义的那段代码只是复制粘贴到您调用/使用该宏的任何地方。如果它包含有效的 C/C++ 代码,那么一旦预处理器完成,它也会在下一步中编译。

    1. “name##_L”和“name##L”是什么意思?,尤其是“##”。

    是的,从前我也对这个## 感到惊讶。这是令牌连接。基本上,您可以使用它生成“以编程方式”变量/函数名称。例如,你有,

    #include <stdio.h> 
    #define concat(a, b) a##b 
    int main(void) 
    { 
        int foobar = 1; 
        printf("%d", concat(foo, bar)); 
        return 0; 
    } 
    

    将打印1,因为它连接 foobar 一起形成foobar

    1. 什么是“__ACCESS_20BIT_REG__ name”?

    __ACCESS_20BIT_REG__ 似乎是一个 implementation-defined 宏,可能在代码的其他地方定义。 __ 双下划线后跟一个大写字母是为实现而保留的(例如,您的 uC 制造商可以使用它们)。

    1. 这个新的“@”符号是什么,我用谷歌搜索了这个符号,我在任何地方都找不到与 @ 符号相关的东西,有人说这不是 C 标准,这是真的吗?是什么意思?

    这个也让我难过。我不认为这是合法的 C.

    编辑:

    实际上,关于您的第 4 点,即 @ synmbol,我在 Google 上搜索了更多内容并找到了此 stackoverflow question

    【讨论】:

      【解决方案3】:

      我认为解释这段代码的最佳方法是尝试一个示例: 那么定义DEFA(name, address) 的结果是什么?

      使用定义,例如DEFA(myName, myAddress) 预处理器创建这些代码行:

      __no_init union
      {
        struct
        {
          volatile unsigned char  myName_L;
          volatile unsigned char  myName_H;
        };
        struct
        {
          volatile unsigned short myNameL;
          volatile unsigned short myNameH;
        };
        __ACCESS_20BIT_REG__ myName;
      } @ myAddress;
      

      现在回答你的问题:

      1. 如图所示解释
      2. 这称为标记连接,是将给定标记连接到一个宏变量en.wikipedia.org/wiki/C_preprocessor#Token_concatenation
      3. ACCESS_20BIT_REG 可能是一个 20 位长的数据类型宏,它在其他地方定义了

      【讨论】:

      • 4 非常相关,因为这是从寄存器内存映射中获取的硬件寄存器定义。没有它,宏将毫无用处。
      • 所以,这就是我的推断,如果我错了,请纠正我......整个UNION占用4个字节,myName_L指向0-7位,myName_H指向8-15位,myNameL指向0-15位,myNameH指向16-31位,myName指向0-19位..,myAddress是第0位的地址?
      猜你喜欢
      • 1970-01-01
      • 2012-01-29
      • 1970-01-01
      • 2019-09-27
      • 1970-01-01
      • 1970-01-01
      • 2019-06-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多