【问题标题】:How can I perform pre-main initialization in C/C++ with avr-gcc?如何使用 avr-gcc 在 C/C++ 中执行预主初始化?
【发布时间】:2010-10-31 07:07:27
【问题描述】:

为了确保一些初始化代码在main之前运行(使用Arduino/avr-gcc)我有如下代码:

class Init {
public:
    Init() { initialize(); }
};

Init init;

理想情况下,我希望能够简单地写:

initialize();

但这不会编译...

有没有更简洁的方法来达到同样的效果?

注意:代码是 Arduino 草图的一部分,因此 main 函数会自动生成且无法修改(例如,在任何其他代码之前调用 initialize)。

更新:理想情况下,初始化将在 setup 函数中执行,但在这种情况下,在 main 之前还有其他代码依赖于它。

【问题讨论】:

    标签: c++ c initialization arduino avr-gcc


    【解决方案1】:

    您可以使用 GCC 的 constructor attribute 来确保它在 main() 之前被调用:

    void Init(void) __attribute__((constructor));
    void Init(void) { /* code */ }  // This will always run before main()
    

    【讨论】:

    【解决方案2】:

    你可以通过给“initialize”一个返回类型,并使用它来初始化一个全局变量来使上面的内容稍微短一些:

    int initialize();
    int dummy = initialize();
    

    但是,您需要注意这一点,标准不保证上述初始化(或您的 init 对象的初始化)发生在 main 运行之前(3.6.2/3):

    命名空间范围对象的动态初始化(8.5、9.4、12.1、12.6.1)是否在main的第一条语句之前完成,由实现定义。

    唯一可以保证的是初始化将在使用 'dummy' 之前进行。

    一个更具侵入性的选项(如果可能的话)可能是在您的 makefile 中使用“-D main=avr_main”。然后,您可以按如下方式添加自己的 main:

    // Add a declaration for the main declared by the avr compiler.
    int avr_main (int argc, const char * argv[]);  // Needs to match exactly
    
    #undef main
    int main (int argc, const char * argv[])
    {
      initialize ();
      return avr_main (argc, argv);
    }
    

    至少在这里你可以保证初始化会在你期望的时候发生。

    【讨论】:

    • 不幸的是,Arduino 环境控制着编译器的执行,所以没有我可以更改的 makefile。
    【解决方案3】:

    这是实现此目的的一种有点邪恶的方法:

    #include <stdio.h>
    
    static int bar = 0;
    
    int __real_main(int argc, char **argv);
    
    int __wrap_main(int argc, char **argv)
    {
        bar = 1;
        return __real_main(argc, argv);
    }
    
    int main(int argc, char **argv)
    {
        printf("bar %d\n",bar);
        return 0;
    }
    

    将以下内容添加到链接器标志中:--wrap main

    例如。

    gcc -Xlinker --wrap -Xlinker main a.c
    

    链接器会将所有对main 的调用替换为对__wrap_main 的调用,请参阅--wrap 上的ld man page

    【讨论】:

    • 不幸的是,我无法控制 gcc(实际上是 avr-gcc)命令行,因为这是由 Arduino 环境自动处理的。
    【解决方案4】:

    您的解决方案简单明了。您还可以做的是将您的代码放在匿名命名空间中。我认为没有必要让它变得更好:)

    【讨论】:

    • 将它包装在匿名命名空间中可能会防止类的名称及其实例污染命名空间(这是一件好事),但它并不能真正帮助减少代码的冗长......
    【解决方案5】:

    如果你使用的是Arduino环境,有什么原因不能放在setup method中吗?

    当然,这是在 Arduino 特定硬件设置之后,所以如果你有这么低级的东西,它真的必须在 main 之前,那么你需要一些构造函数。

    更新:

    好的,如果它必须在 main 之前完成,我认为唯一的方法是使用你已经使用的构造函数。

    你总是可以为它制作一个预处理器宏:

    #define RUN_EARLY(code) \
    namespace { \
        class Init { \
            Init() { code; } \
        }; \
        Init init; \
    }
    

    现在应该可以了:

    RUN_EARLY(initialize())
    

    但这并没有真正让事情变得更短,只是移动了冗长的代码。

    【讨论】:

    • +1,但不幸的是我不能为此使用 setup()。我会更新问题以明确这一点。
    【解决方案6】:

    您可以使用“.init*”部分添加要在 main() 之前运行的 C 代码(甚至是 C 运行时)。这些部分最后链接到可执行文件中,并在程序初始化期间的特定时间调用。您可以在此处获取列表:

    http://www.nongnu.org/avr-libc/user-manual/mem_sections.html

    例如,

    .init1 弱绑定到 __init(),因此如果定义 __init(),它将被链接并首先调用。但是,堆栈尚未设置,因此您必须小心操作(仅使用 register8_t 变量,不调用任何函数)。

    【讨论】:

      【解决方案7】:

      使用类的静态成员。它们在进入 main 之前被初始化。缺点是无法控制静态类成员的初始化顺序。

      这是您的示例转换:

      class Init {
      private:
          // Made the constructor private, so to avoid calling it in other situation
          // than for the initialization of the static member.
          Init() { initialize(); }
      
      private:
          static Init INIT;
      };
      
      
      Init Init::INIT;
      

      【讨论】:

      • 这更详细,而不是更少。
      • 这个想法让我想知道跨类层次结构的静态成员的初始化顺序是否在C++中定义?如果是这样,那将是解决“静态初始化顺序惨败”的一种方法。
      • @Piotr:翻译单元中的初始化顺序被定义为“定义出现的顺序”。在翻译单元之间未定义。
      • @Catalin:静态成员的初始化与非本地对象完全相同,即。保证在使用它们之前被初始化,但不一定在 main 之前被初始化。您的示例基本上与问题中的示例具有相同的行为。 (见 9.4.2/7 参考 3.6.2、3.6.3)。
      【解决方案8】:

      当然,你把它放在你的一个头文件中,比如 preinit.h:

      class Init { public: Init() { initialize(); } }; Init init;
      

      然后,在您的一个编译单元中,输入:

      void initialize(void) {
          // weave your magic here.
      }
      #include "preinit.h"
      

      我知道这是一个杂项,但我不知道在不使用在文件范围内执行的类构造函数的情况下,任何可移植的方式来进行预主初始化。

      您还应该小心包含多个这些初始化函数,因为我不相信 C++ 规定了顺序 - 它可能是随机的。

      我不确定您所说的这个“草图”,但是否可以在将主编译单元传递给编译器之前使用脚本对其进行转换,例如:

      awk '{print;if (substr($0,0,11) == "int main (") {print "initialize();"};}'
      

      您可以看到这将如何影响您的程序,因为:

      echo '#include <stdio.h>
      int main (void) {
          int x = 1;
          return 0;
      }' | awk '{
          print;
          if (substr($0,0,11) == "int main (") {
              print "    initialize();"
          }
      }'
      

      通过添加initialize() 调用生成以下内容:

      #include <stdio.h>
      int main (void) {
          initialize();
          int x = 1;
          return 0;
      }
      

      您可能无法对生成的文件进行后处理,在这种情况下您应该忽略最后一个选项,但这是我首先要考虑的。

      【讨论】:

      • 解决方案不需要是可移植的。它只需要使用 avr-gcc 即可。
      【解决方案9】:

      下面是我如何执行预主编码。 在 main 之前执行了一些服务器 init 部分,指的是http://www.nongnu.org/avr-libc/user-manual/mem_sections.html initN 部分。

      无论如何,由于某种原因,这只适用于 -O0 优化。我仍在尝试找出哪个选项“优化”了我的预主汇编代码。

      static void
      __attribute__ ((naked))
      __attribute__ ((section (".init8")))    /* run this right before main */
      __attribute__ ((unused))    /* Kill the unused function warning */
      stack_init(void) {assembly stuff}
      

      更新,原来我声称这个函数没有被使用,导致优化例程消失。我打算杀死未使用的功能警告。它被固定为使用过的属性。

      【讨论】:

        猜你喜欢
        • 2012-05-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-21
        • 1970-01-01
        • 1970-01-01
        • 2013-07-04
        相关资源
        最近更新 更多