【问题标题】:How to execute some code before entering the main() routine in VC?如何在进入 VC 中的 main() 例程之前执行一些代码?
【发布时间】:2010-10-18 06:06:37
【问题描述】:

我正在阅读微软的 CRT 源代码,我可以想出以下代码,其中函数 __initstdio1 将在 main() 例程之前执行。

问题是,在VC中进入main()例程之前如何执行一些代码(不是VC++代码)?

#include <stdio.h>

#pragma section(".CRT$XIC",long,read)

int __cdecl __initstdio1(void);

#define _CRTALLOC(x) __declspec(allocate(x))

_CRTALLOC(".CRT$XIC") static pinit = __initstdio1;

int z = 1;

int __cdecl __initstdio1(void) {
    z = 10;
    return 0;
}

int main(void) {
    printf("Some code before main!\n");
    printf("z = %d\n", z);
    printf("End!\n");
    return 0;
}

输出将是:

Some code before main!
z = 10
End!

但是,我无法理解代码。

我在 .CRT$XIC 上做了一些谷歌搜索,但没有找到运气。哪位高手可以给我解释一下上面的代码段,尤其是以下代码:

  1. _CRTALLOC(".CRT$XIC") static pinit = __initstdio1; 这一行是什么意思?变量pinit有什么意义?
  2. 在编译期间,编译器 (cl.exe) 会抛出如下警告:

Microsoft (R) 32 位 C/C++ 优化编译器版本 15.00.30729.01 用于 80x86 版权所有 (C) 微软公司。保留所有权利。

stdmacro.c
stdmacro.c(9) : warning C4047: 'initializing' : 'int' differs in levels of indirection from 'int (__
cdecl *)(void)'
Microsoft (R) Incremental Linker Version 9.00.30729.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:stdmacro.exe
stdmacro.obj

需要采取什么纠正措施来删除警告消息?

提前致谢。


添加:

我已经修改了代码并将 pinit 的类型指定为 _PIFV。现在警告信息消失了。

新代码如下:

#include <stdio.h>

#pragma section(".CRT$XIC1",long,read)

int __cdecl __initstdio1(void);

typedef int  (__cdecl *_PIFV)(void);

#define _CRTALLOC(x) __declspec(allocate(x))

_CRTALLOC(".CRT$XIC1") static _PIFV pinit1 = __initstdio1;

int z = 1;

int __cdecl __initstdio1(void) {
    z = 100;

    return 0;
}

int main(void) {
    printf("Some code before main!\n");
    printf("z = %d\n", z);
    printf("End!\n");
    return 0;
}

【问题讨论】:

    标签: c msvcrt crt


    【解决方案1】:

    一个简单的方法来做到这一点。

    #include <iostream>
    
    int before_main()
    {
        std::cout << "before main" << std::endl;
        return 0;
    }
    
    static int n = before_main();
    
    void main(int argc, char* argv[])
    {
        std::cout << "in main" << std::endl;
    }
    

    【讨论】:

    • 当 before_main() 依赖于在 main() 之前运行的另一个函数中创建的数据时,可能会出现问题。此类函数的运行顺序未定义。这尤其意味着 before_main() 创建的全局数据可能会被将全局数据初始化为 0 的运行时例程覆盖
    • 用 puts() 替换 std::count
    • @dmityugov,C 编译器没有 static int n = before_main(); 这样的东西。 GCC 有一个__attribute__ ((constructor)),但它是不可移植的。
    【解决方案2】:

    这就是 _CRTALLOC 的定义:

    extern _CRTALLOC(".CRT$XIA") _PVFV __xi_a[];
    extern _CRTALLOC(".CRT$XIZ") _PVFV __xi_z[];// C initializers
    extern _CRTALLOC(".CRT$XCA") _PVFV __xc_a[];
    extern _CRTALLOC(".CRT$XCZ") _PVFV __xc_z[];// C++ initializers
    

    这是一个要预初始化的表格,其中放置了指向您的函数 __initstdio1 的指针。

    此页面描述了 CRT 初始化:

    http://msdn.microsoft.com/en-us/library/bb918180.aspx

    【讨论】:

      【解决方案3】:

      至少在 C++ 中,您不需要所有特定于实现的东西:

      #include <iostream>
      
      struct A {
         A() { std::cout << "before main" << std::endl; }
      };
      
      A a;
      
      int main() {
         std::cout << "in main" << std::endl;
      }
      

      【讨论】:

      • 这是个好主意。但是您的代码只能通过 C++ 编译;不在 C 中。
      【解决方案4】:

      不久前,我在 CodeGuru 上写了一篇获奖的 article

      【讨论】:

        【解决方案5】:

        有一些信息here(搜索CRT)。变量pinit 的意义是没有的,它只是放在可执行文件中的一段数据,运行时可以在其中找到它。但是,我建议您给它一个类型,如下所示:

        _CRTALLOC(".CRT$XIC") static void (*pinit)()=...
        

        链接器警告可能只是警告您有一个返回类型为 int 的函数,但不返回任何内容(可能您最好将返回类型更改为 void)。

        【讨论】:

          【解决方案6】:

          即使在 C 中,也需要在输入 main() 之前运行一些代码,如果只是将命令行转换为 C 调用约定。在实践中,标准库需要一些初始化,具体的需求可能因编译而异。

          真正的程序入口点是在链接时设置的,由于历史原因,它通常位于名为crt0 之类的模块中。如您所见,该模块的源代码可在 crt 源代码中找到。

          为了支持在链接时发现的初始化,使用了一个特殊的段。它的结构是一个固定签名的函数指针列表,会在crt0的早期迭代和每个函数调用。在 C++ 链接中使用相同的函数指针数组(或非常相似的数组)来保存指向全局对象构造函数的指针。

          链接器通过允许链接的每个模块在其中包含数据来填充数组,这些数据全部连接在一起以形成最终可执行文件中的段。 变量pinit 的唯一意义是它被声明(通过_CRTALLOC() 宏)位于该段中,并被初始化为C 启动期间要调用的函数的地址。

          显然,这方面的细节是非常特定于平台的。对于一般编程,最好将初始化和当前 main 包装在新的 main() 中:

          int main(int argc, char **argv) {
              early_init();
              init_that_modifies_argv(&argc, &argv);
              // other pre-main initializations...
              return real_main(argc,argv);
          }
          

          对于特殊目的,修改crt0 模块本身或执行特定于编译器的技巧来调用额外的早期初始化函数可能是最佳答案。例如,当构建在没有操作系统加载程序的情况下从 ROM 运行的嵌入式系统时,通常需要自定义 crt0 模块的行为,以便完全有一个堆栈来将参数推送到 main() .在这种情况下,可能没有比修改crt0 以初始化内存硬件以满足您的需求更好的解决方案了。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-06-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多