【问题标题】:What's the shortest code to write directly to a memory address in C/C++?在 C/C++ 中直接写入内存地址的最短代码是什么?
【发布时间】:2010-03-10 13:32:10
【问题描述】:

我正在为没有内存保护的嵌入式系统编写系统级代码(在 ARM Cortex-M1 上,使用 gcc 4.3 编译)并且需要直接读取/写入内存映射寄存器。到目前为止,我的代码如下所示:

#define UART0     0x4000C000
#define UART0CTL  (UART0 + 0x30)

volatile unsigned int *p;
p = UART0CTL;
*p &= ~1;

有没有不使用指针的更短的方法(我的意思是代码更短)?我正在寻找一种方法来编写如此短的实际分配代码(如果我不得不使用更多#defines 就可以了):

*(UART0CTL) &= ~1;

到目前为止,我尝试的任何事情都以 gcc 抱怨它无法将某些东西分配给左值...

【问题讨论】:

  • #define UART0CT#define X?
  • @The Machine Charmer:+1,有趣。我不认为 OP 在代码高尔夫时尚中意味着更短,但是,很好。 :-)
  • 标准 C 要求在 ordetr 中进行显式转换,以将非零整数分配给指针。您的代码以其当前形式仅在 GCC 中有效,但从迂腐 C 的角度来看是不正确的。因此,无论您是否愿意,如果您真的想将其保留为有效的 C,则必须将其延长。当然,如果您想要特定于 GCC 的解决方案,那就另当别论了。
  • @PratikDeoghare 那...那是搞笑哈哈!

标签: c++ c gcc systems-programming


【解决方案1】:
#define UART0CTL ((volatile unsigned int *) (UART0 + 0x30))

:-P

编辑添加:哦,为了回应所有关于如何将问题标记为 C++ 和 C 的问题,这里有一个 C++ 解决方案。 :-P

inline unsigned volatile& uart0ctl() {
    return *reinterpret_cast<unsigned volatile*>(UART0 + 0x30);
}

这可以直接卡在头文件中,就像 C 风格的宏一样,但你必须使用函数调用语法来调用它。

【讨论】:

  • @maligree:强制转换是 C 中的基本操作之一,绝对不是“更精细的点”。 :-P (这不是对你的戳,事实上,C 似乎需要大量的转换才能完成真正的工作。:-P)
  • 当然,但我的意思是将所有内容组合成一个单一但仍然“人类可读”的行的艺术。使用您的解决方案,IMO 我最终得到了可以立即看到语义的漂亮代码(好吧,我仍然可以#define UARTDISABLE ~1 为该行添加更多语义......)
  • 这是C 的一个点,绝对不是C++ 的一个点。我会猛烈抨击在C++ 中使用#define 作为常量的任何人。当存在替代品时,最好避免使用宏。
  • 这被标记为 C++,但文本表明它可能只是 C。如果是 C++,你应该使用 reinterpret_cast 来进行搜索。
  • @Mark, @Matthieu:我添加了一个 C++ 版本只是为了您的娱乐。 :-)
【解决方案2】:

我想成为一个挑剔的人:我们是在谈论 C 还是 C++?

如果是 C,我愿意听从 Chris 的回答(我希望删除 C++ 标签)。

如果是 C++,我建议不要使用那些讨厌的 C-Cast 和 #define

惯用的 C++ 方式是使用全局变量:

volatile unsigned int& UART0 = *((volatile unsigned int*)0x4000C000);
volatile unsigned int& UART0CTL = *(&UART0 + 0x0C);

我声明了一个类型化全局变量,它将遵守范围规则(与宏不同)。

使用方便(无需使用*()),因此更短!

UART0CTL &= ~1; // no need to dereference, it's already a reference

如果你希望它是指针,那么它将是:

volatile unsigned int* const UART0 = 0x4000C000; // Note the const to prevent rebinding

但是使用不能为 null 的 const 指针有什么意义呢?这就是为其创建引用的语义原因。

【讨论】:

  • 因为您可以将 const 指针放在头文件中,并从 C 和 C++ 中#include 它。不妨在 C 中也将其声明为“静态”(这在 C++ 中也是无害的),因为它不需要在符号表中结束。
  • 没错,我只想到了问题的C++ 方面,因为#defineC 程序中是传统的。
  • 嗯,在我看来,这似乎是我项目的最佳答案,因为我没有理由不相信关于“惯用 C++ 方式”的说法......
【解决方案3】:

如果你想让硬件寄存器看起来像普通的旧变量,你可以比 Chris 的回答更进一步:

#define UART0     0x4000C000
#define UART0CTL (*((volatile unsigned int *) (UART0 + 0x30)))

UART0CTL &= ~1;

这可能是更可取的口味问题。我曾在团队希望寄存器看起来像变量的情况下工作,并且我已经处理过添加的取消引用被认为“隐藏太多”的代码,因此寄存器的宏将作为指针保留被明确地取消引用(如克里斯的回答)。

【讨论】:

    【解决方案4】:
    #define UART0  ((volatile unsigned int*)0x4000C000)
    #define UART0CTL (UART0 + 0x0C)
    

    【讨论】:

    • @dkrueger:0x0C 仅适用于 32 位指针。诚然,这很可能是 ARM 的情况,但仍然非常不便携。 :-P
    • @Chris:这并不重要,整个事情一开始就非常不便携。 (但是,0x30 / sizeof(int) 会更清楚,恕我直言。)
    • @KennyTM + 10 是由于我最初误读了偏移量。 @Chris 在他指定的平台上,无符号整数是 32 位的。 @Roger我认为省略偏移量会使事情变得最清楚:((volatile unsigned int *)0x4000C030),但我试图模仿他的代码的原始形式。
    • +1:我一直在不同的嵌入式项目上使用这个解决方案。
    【解决方案5】:

    我喜欢在结构中指定实际的控制位,然后将其分配给控制地址。比如:

    typedef struct uart_ctl_t {
        unsigned other_bits : 31;
        unsigned disable : 1;
    };
    uart_ctl_t *uart_ctl = 0x4000C030;
    uart_ctl->disable = 1;
    

    (抱歉,如果语法不太正确,我实际上已经有一段时间没有用 C 编写代码了......)

    【讨论】:

    • 我怀疑位域中的位顺序是否由标准指定。我想,这段代码将是实现定义的行为。但是使用struct 的想法很好:定义一个包含内存映射设备的内存布局的struct uart,将地址分配给单个指针uart* const uart_regs = 0x4000c000;,然后使用uart_regs-&gt;ctl &amp;= ~1; 通过该对象进行访问。
    【解决方案6】:

    我有点喜欢嵌入式应用程序的另一个选项是使用链接器为您的硬件设备定义部分并将您的变量映射到这些部分。这样做的好处是,如果您面向多个设备,即使来自同一供应商(例如 TI),您通常也必须逐个设备更改链接器文件。即同一系列中的不同设备具有不同数量的内部直接映射内存,并且板对板您可能在不同位置也有不同数量的 RAM 和硬件。这是 GCC 文档中的一个示例:

    通常,编译器将其生成的对象放在部分中 像数据和bss。但是,有时您需要额外的部分, 或者你需要某些特定的变量出现在特殊的 部分,例如映射到特殊硬件。这部分 属性指定变量(或函数)存在于 特定部分。比如这个小程序使用了几个 具体部分名称:

          struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
          struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
          char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
          int init_data __attribute__ ((section ("INITDATA")));
    
          main()
          {
            /* Initialize stack pointer */
            init_sp (stack + sizeof (stack));
    
            /* Initialize initialized data */
            memcpy (&init_data, &data, &edata - &data);
    
            /* Turn on the serial ports */
            init_duart (&a);
            init_duart (&b);
          }
    

    使用带全局变量而不是局部变量的节属性,如示例所示。

    您可以使用已初始化或未初始化的 section 属性 全局变量,但链接器要求每个对象定义一次, 除了未初始化的变量暂时进入 common(或 bss)部分,可以多次“定义”。使用 section 属性将更改变量进入的部分和 如果未初始化的变量可能会导致链接器发出错误 有多种定义。您可以强制初始化变量 带有 -fno-common 标志或 nocommon 属性。

    【讨论】:

      猜你喜欢
      • 2017-04-16
      • 2011-12-03
      • 1970-01-01
      • 1970-01-01
      • 2021-03-09
      • 1970-01-01
      • 2020-07-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多