Является ли main () действительно началом программы на C ++?

131

Раздел $ 3.6.1 / 1 стандарта C ++ гласит:

Программа должна содержать глобальную функцию с именем main , которая является назначенным запуском программы.

Теперь рассмотрим этот код,

int square(int i) { return i*i; }
int user_main()
{ 
    for ( int i = 0 ; i < 10 ; ++i )
           std::cout << square(i) << endl;
    return 0;
}
int main_ret= user_main();
int main() 
{
        return main_ret;
}

Этот пример кода выполняет то, что я намереваюсь сделать, то есть печатает квадрат целых чисел от 0 до 9 перед входом в main()функцию, которая должна быть «запуском» программы.

Я также скомпилировал его с -pedanticопцией GCC 4.5.0. Это не выдает ошибок, даже предупреждений!

Итак, мой вопрос:

Действительно ли этот код соответствует стандарту?

Если он соответствует стандарту, разве это не отменяет того, что говорит стандарт? main()это не запуск этой программы! user_main()выполнен до main().

Я понимаю , что для инициализации глобальную переменную main_ret, в use_main()сначала выполняется , но это совсем другое дело; Дело в том, что он делает недействительным цитируемый оператор $ 3.6.1 / 1 из Стандарта, поскольку main()НЕ является началом программы; это фактически конец из этой программы!


РЕДАКТИРОВАТЬ:

Как вы определяете слово «старт»?

Все сводится к определению слова «запуск программы» . Так как именно вы это определяете?

Наваз
источник

Ответы:

85

Нет, C ++ делает много вещей для «установки среды» до вызова main; однако main - это официальный запуск "определяемой пользователем" части программы C ++.

Некоторые настройки среды неуправляемы (например, исходный код для настройки std :: cout; однако, некоторые из сред можно контролировать, например статические глобальные блоки (для инициализации статических глобальных переменных). Обратите внимание, что, поскольку у вас нет полной control до main, у вас нет полного контроля над порядком инициализации статических блоков.

После main ваш код концептуально «полностью контролирует» программу в том смысле, что вы можете как указать инструкции, которые должны быть выполнены, так и порядок их выполнения. Многопоточность может изменять порядок выполнения кода; но вы по-прежнему контролируете C ++, потому что вы указали, что разделы кода выполняются (возможно) вне очереди.

Эдвин Бак
источник
9
+1 для этого »Обратите внимание, что, поскольку у вас нет полного контроля до main, у вас нет полного контроля над порядком инициализации статических блоков. После main ваш код концептуально« полностью контролирует » программа в том смысле, что вы можете указать инструкции, которые должны быть выполнены, и порядок, в котором они должны выполняться » . Это также заставляет меня пометить этот ответ как принятый ... Я думаю, что это очень важные моменты, которые достаточно оправдывают main()как «начало программы»
Наваз
13
@Nawaz: обратите внимание, что помимо полного контроля над порядком инициализации, вы не можете контролировать ошибки инициализации: вы не можете перехватывать исключения в глобальной области.
Андре Карон,
@Nawaz: Что такое статические глобальные блоки? не могли бы вы объяснить это на простом примере? Спасибо
Destructor
@meet: Объекты, объявленные на уровне пространства имен, имеют staticпродолжительность хранения, и поэтому эти объекты, принадлежащие разным единицам трансляции, могут быть инициализированы в любом порядке (поскольку порядок не определен стандартом). Я не уверен, что это ответ на ваш вопрос, хотя именно это я мог бы сказать в контексте этой темы.
Nawaz
88

Вы неправильно читаете предложение.

Программа должна содержать глобальную функцию с именем main, которая является назначенным запуском программы.

Стандарт ОПРЕДЕЛЯЕТ слово «начало» для остальной части стандарта. Это не говорит о том, что перед вызовом код не выполняется main. В нем говорится, что запуск программы считается на функции main.

Ваша программа соответствует требованиям. Ваша программа не «запустилась» до тех пор, пока не запустится main. Конструктор вызывается до того, как ваша программа "запускается" в соответствии с определением "start" в стандарте, но это не имеет значения. МНОГО кода выполняется перед mainкак всегда вызывается в каждой программе, а не только этот пример.

Для целей обсуждения код вашего конструктора выполняется до «запуска» программы, и это полностью соответствует стандарту.

Адам Дэвис
источник
3
Извините, но я не согласен с вашей интерпретацией этого пункта.
Гонки за легкостью на орбите
Я думаю, что Адам Дэвис прав, «main» больше похоже на какие-то ограничения кода.
laike9m
@LightnessRacesinOrbit Я никогда не отслеживал, но для меня это предложение можно логически свести к «глобальной функции с именем main - это назначенное начало программы» (курсив мой). Как вы интерпретируете это предложение?
Адам Дэвис,
1
@AdamDavis: Я не помню, что меня беспокоило. Я не могу вспомнить ни одного сейчас.
Гонки за легкостью на орбите
23

Ваша программа не будет связываться и, следовательно, не будет работать, если не будет файла main. Однако main () не вызывает начало выполнения программы, потому что у объектов на уровне файла есть конструкторы, которые запускаются заранее, и можно было бы написать всю программу, которая запускает свое время жизни до достижения main (), и позволить самой main иметь пустое тело.

На самом деле, чтобы обеспечить это, вам понадобится один объект, созданный до main, и его конструктор для вызова всего потока программы.

Посмотри на это:

class Foo
{
public:
   Foo();

 // other stuff
};

Foo foo;

int main()
{
}

Поток вашей программы будет эффективно вытекать из Foo::Foo()

Дойная корова
источник
13
+1. Но обратите внимание, что если у вас есть несколько глобальных объектов в разных единицах перевода, это быстро вызовет проблемы, поскольку порядок, в котором вызываются конструкторы, не определен. Вы можете обойтись без синглтонов и ленивой инициализации, но в многопоточной среде все быстро становится ужасно. Одним словом, не делайте этого в реальном коде.
Alexandre C.
3
Хотя вы, вероятно, должны указать main () правильное тело в своем коде и позволить ему запускать выполнение, концепция объектов за пределами этого запуска - это то, на чем основано множество библиотек LD_PRELOAD.
CashCow
2
@Alex: в стандарте указано undefined, но на практике порядок ссылок (обычно в зависимости от компилятора) регулируется порядком инициализации.
ThomasMcLeod
1
@ Томас: Я бы даже отдаленно не стал на это полагаться. Я также, конечно, не стал бы пытаться вручную управлять системой сборки.
Alexandre C.
1
@Alex: уже не так важно, но раньше мы использовали порядок ссылок для управления образом сборки, чтобы уменьшить подкачку физической памяти. Есть и другие побочные причины, по которым вы можете захотеть контролировать порядок запуска, даже если он не влияет на семантику программы, например, сравнительное тестирование производительности при запуске.
ThomasMcLeod
15

Вы также отметили вопрос как «C», тогда, строго говоря о C, ваша инициализация должна завершиться неудачно в соответствии с разделом 6.7.8 «Инициализация» стандарта ISO C99.

Наиболее актуальным в этом случае кажется ограничение №4, которое гласит:

Все выражения в инициализаторе для объекта, который имеет статическую продолжительность хранения, должны быть постоянными выражениями или строковыми литералами.

Итак, ответ на ваш вопрос заключается в том, что код не соответствует стандарту C.

Вы, вероятно, захотели бы удалить тег «C», если бы вас интересовал только стандарт C ++.

Remo.D
источник
4
@ Remo.D не могли бы вы рассказать нам, что находится в этом разделе. Не у всех из нас есть стандарт Си :).
UmmaGumma
2
Поскольку вы такой разборчивый: Увы, ANSI C устарел с 1989 года. Соответствующие стандарты - это ISO C90 или C99.
Lundin
@Lundin: Никто не бывает достаточно разборчивым :) Я читал ISO C99, но уверен, что это применимо и к C90.
Remo.D
@Выстрел. Вы правы, добавил предложение, которое я считаю наиболее актуальным.
Remo.D
3
@Remo: +1 за информацию о том, что это недействительно C; я этого не знал. Посмотрите, как люди учатся, иногда по плану, иногда случайно!
Nawaz
10

Раздел 3.6 в целом очень четко описывает взаимодействие mainи динамические инициализации. «Назначенный запуск программы» больше нигде не используется и просто описывает общее назначение main(). Нет никакого смысла интерпретировать эту фразу нормативным образом, который противоречит более подробным и ясным требованиям Стандарта.

aschepler
источник
9

Компилятору часто приходится добавлять код перед main (), чтобы соответствовать стандарту. Поскольку в стандарте указано, что инициализация глобальных переменных / статик должна выполняться до выполнения программы. Как уже упоминалось, то же самое касается конструкторов объектов, размещенных в области видимости файла (глобальных объектов).

Таким образом, исходный вопрос является отношением к C , а также, потому что в программе C вы бы еще глобал / статических инициализации , чтобы сделать до того , как программа может быть запущена.

Стандарты предполагают, что эти переменные инициализируются с помощью «магии», потому что они не говорят, как они должны быть установлены перед инициализацией программы. Я думаю, они считали это чем-то выходящим за рамки стандарта языка программирования.

Изменить: см., Например, ISO 9899: 1999 5.1.2:

Все объекты со статической продолжительностью хранения должны быть инициализированы (установлены в исходные значения) перед запуском программы. В остальном способ и время такой инициализации не определены.

Теория, лежащая в основе этой «магии», восходит к зарождению C, когда это был язык программирования, предназначенный для использования только для ОС UNIX на компьютерах с ОЗУ. Теоретически программа сможет загрузить все предварительно инициализированные данные из исполняемого файла в ОЗУ одновременно с загрузкой самой программы в ОЗУ.

С тех пор компьютеры и ОС эволюционировали, и C используется в гораздо более широкой области, чем предполагалось изначально. Современная ОС ПК имеет виртуальные адреса и т. Д., И все встроенные системы выполняют код из ПЗУ, а не из ОЗУ. Так что есть много ситуаций, когда оперативную память нельзя настроить «автоматически».

Кроме того, стандарт слишком абстрактен, чтобы что-либо знать о стеках, памяти процесса и т. Д. Эти вещи также должны быть выполнены до запуска программы.

Следовательно, почти каждая программа на C / C ++ имеет некоторый код инициализации / копирования, который выполняется перед вызовом main, чтобы соответствовать правилам инициализации стандартов.

Например, встроенные системы обычно имеют параметр, называемый «запуск без соответствия ISO», при котором вся фаза инициализации пропускается по соображениям производительности, а затем код фактически запускается непосредственно из main. Но такие системы не соответствуют стандартам, поскольку вы не можете полагаться на значения инициализации глобальных / статических переменных.

Лундин
источник
4

Ваша «программа» просто возвращает значение из глобальной переменной. Все остальное - это код инициализации. Таким образом, стандарт выполняется - у вас просто очень тривиальная программа и более сложная инициализация.

Зак Хоуленд
источник
2

Похоже на придирку с английской семантикой. ОП называет свой блок кода сначала «кодом», а затем «программой». Пользователь пишет код, а затем компилятор пишет программу.

dSerk
источник
1

main вызывается после инициализации всех глобальных переменных.

Стандарт не определяет порядок инициализации всех глобальных переменных всех модулей и статически связанных библиотек.

vz0
источник
0

Да, main - это «точка входа» для каждой программы на C ++, за исключением расширений, специфичных для реализации. Тем не менее, некоторые вещи происходят до main, особенно глобальная инициализация, например, main_ret.

Фред Нурк
источник