Как выполнить некоторый код перед входом в процедуру main() в VC?

Я читаю исходный код Microsoft CRT и могу придумать следующий код, в котором функция __initstdio1 будет выполняться перед подпрограммой main().

Вопрос в том, как выполнить некоторый код перед входом в процедуру main() в VC (не код 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 Copyright (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

Какие корректирующие действия необходимо выполнить, чтобы удалить предупреждающее сообщение?

Заранее спасибо.


Добавлен:

Я изменил код и задал тип для ввода как _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;
}

person yinyueyouge    schedule 08.04.2009    source источник


Ответы (6)


Некоторая информация есть здесь (ищите CRT). Значения переменной pinit нет, это просто фрагмент данных, помещенный в исполняемый файл, где среда выполнения может его найти. Тем не менее, я бы посоветовал вам дать ему тип, например:

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

Предупреждение компоновщика, вероятно, просто предупреждает вас о том, что у вас есть функция, которая имеет тип возвращаемого значения int, но ничего не возвращает (вероятно, вам лучше изменить тип возвращаемого значения на void).

person jpalecek    schedule 08.04.2009

Простой способ сделать это.

#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;
}
person Shino C G    schedule 08.04.2009
comment
проблемы могут возникнуть, когда функция before_main() зависит от данных, созданных в другой функции, которая запускается до функции main(). Порядок выполнения таких функций не определен. Это, в частности, означает, что глобальные данные, которые создает функция before_main(), могут быть перезаписаны подпрограммами времени выполнения, которые инициализируют глобальные данные нулем. - person dmityugov; 08.04.2009
comment
замените std::count ‹‹ на puts(), и это будет C - person dmityugov; 09.04.2009
comment
@dmityugov, нет такой вещи, как static int n = before_main(); с компилятором C. В GCC есть __attribute__ ((constructor)), но он не переносим. - person malat; 05.02.2015

Это то, что _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

person Mark Ingram    schedule 08.04.2009

По крайней мере, в С++ вам не нужны все эти специфичные для реализации вещи:

#include <iostream>

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

A a;

int main() {
   std::cout << "in main" << std::endl;
}
person Community    schedule 08.04.2009
comment
Это хорошая идея. Но ваш код может пройти компиляцию только на C++; не в С. - person yinyueyouge; 08.04.2009

Я написал отмеченную наградами статью об этом на CodeGuru некоторое время назад.

person Community    schedule 11.03.2010

Даже в 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 или выполнение специфичных для компилятора трюков для вызова дополнительных функций ранней инициализации. Например, при построении встраиваемых систем, которые запускаются из ПЗУ без загрузчика операционной системы, обычно требуется настроить поведение модуля crt0, чтобы вообще иметь стек, в который можно протолкнуть параметры в main(). В этом случае не может быть лучшего решения, чем изменить crt0 для инициализации оборудования памяти в соответствии с вашими потребностями.

person RBerteig    schedule 08.04.2009