8

我正在阅读微软的 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) Microsoft Corporation。版权所有。

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;
}
4

6 回答 6

5

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

#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;
}
于 2009-04-08T08:26:54.980 回答
4

这就是 _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

于 2009-04-08T08:05:04.220 回答
3

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

#include <iostream>

struct A {
   A() { std::cout << "before main" << std::endl; }
};

A a;

int main() {
   std::cout << "in main" << std::endl;
}
于 2009-04-08T07:58:18.230 回答
2

不久前,我在 CodeGuru 上写了一篇关于此的获奖文章。

于 2010-03-11T20:13:29.723 回答
1

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

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

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

于 2009-04-08T08:09:24.023 回答
1

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

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

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

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

显然,这方面的细节是非常特定于平台的。对于一般编程,将初始化和当前 main 包装在 new 中可能会更好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以初始化内存硬件以满足您的需要更好的解决方案了。

于 2009-04-08T20:10:41.730 回答