Чи є головним () дійсно запуском програми на C ++?


131

Розділ $ 3.6.1 / 1 із стандарту C ++ читає,

Програма повинна містити глобальну функцію, яку називають основною , яка позначається початком програми.

Тепер розглянемо цей код,

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 ++ робить багато речей для "встановлення середовища" до виклику основного; однак головним є офіційний старт частини програми, визначеної користувачем C ++.

Деякі настройки середовища не контролюються (наприклад, початковий код для налаштування std :: cout; однак, деяке середовище є керованим, як статичні глобальні блоки (для ініціалізації статичних глобальних змінних). керування перед основним, ви не маєте повного контролю над порядком, в якому статичні блоки ініціалізуються.

Після основного, ваш код концептуально "повністю контролює" програму, в тому сенсі, що ви можете вказувати як інструкції, які потрібно виконати, так і порядок їх виконання. Багатопоточна передача може переставити порядок виконання коду; але ви все ще керуєте C ++, оскільки ви вказали, що розділи коду виконуються (можливо) поза порядком.


9
+1 для цього "Зауважте, що оскільки ви не маєте повного контролю до основного, ви не маєте повного контролю над порядком, в якому статичні блоки ініціалізуються. Після головного, ваш код концептуально" повністю контролюється " програма, в тому сенсі, що ви можете вказувати як інструкції для виконання, так і порядок їх виконання " . Це також змушує мене відзначити цю відповідь прийнятою відповіддю ... Я думаю, що це дуже важливі моменти, які достатньо виправдовують main()як "початок програми"
Наваз,

13
@Nawaz: зауважте, що крім повного контролю над порядком ініціалізації ви не маєте контролю над помилками ініціалізації: ви не можете ловити винятки в глобальній області.
Андре Карон

@Nawaz: Що таке статичні глобальні блоки? поясніть, будь ласка, простим прикладом? Спасибі
деструктор

@meet: Об'єкти, оголошені на рівні простору імен, мають staticтривалість зберігання, і як такі ці об'єкти, що належать до різних одиниць перекладу, можуть бути ініціалізовані в будь-якому порядку (оскільки порядок не визначено стандартом). Я не впевнений, чи відповідає це на ваше запитання, хоча це я можу сказати в контексті цієї теми.
Наваз

88

Ви неправильно читаєте речення.

Програма повинна містити глобальну функцію, яку називають основною, яка позначається початком програми.

Стандарт - ВИЗНАЧЕННЯ слово "старт" для цілей, що залишилися від стандарту. Це не говорить про те, що жоден код, який виконується раніше, mainне викликається. Там сказано, що початок програми вважається функцією main.

Ваша програма відповідає. Ваша програма не "запускалася" до початку основної роботи. Конструктор викликається до того, як ваша програма "запуститься" згідно з визначенням "start" у стандарті, але це навряд чи має значення. БАГАТО коду виконується перед mainяк завжди викликається в кожній програмі, а не тільки цей приклад.

З метою обговорення ваш конструкторський код виконується до "запуску" програми, і він повністю відповідає стандарту.


3
Вибачте, але я не згоден з вашим тлумаченням цього пункту.
Гонки легкості по орбіті

Я думаю, що Адам Девіс має рацію, "головне" більше схоже на якісь обмеження кодування.
laike9m

@LightnessRacesinOrbit Я ніколи не продовжував, але, як на мене, це пропозиція може бути логічно зведено до "глобальної функції, яка називається головним - призначений початок програми" (акцент додано). Яке ваше тлумачення цього речення?
Адам Девіс

1
@AdamDavis: Я не пам’ятаю, у чому була моя стурбованість. Я зараз не можу придумати його.
Гонки легкості на орбіті

23

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

Насправді для виконання цього вам слід мати один об'єкт, який будується перед головним та його конструктором, щоб викликати весь потік програми.

Подивись на це:

class Foo
{
public:
   Foo();

 // other stuff
};

Foo foo;

int main()
{
}

Потік вашої програми фактично випливав би з цього Foo::Foo()


13
+1. Але зауважте, що якщо у вас є декілька глобальних об'єктів у різних одиницях перекладу, це швидко утратить вам проблеми, оскільки порядок виклику конструкторів не визначений. Ви можете піти з одиночними кнопками та ледачою ініціалізацією, але в багатопотоковому середовищі все стає дуже некрасивим. Одним словом, не робіть цього в реальному коді.
Олександр С.

3
Хоча вам, мабуть, слід надати main () належне тіло у своєму коді та дозволити йому запускати виконання, концепція об'єктів поза цим запуском - це те, на чому базується багато бібліотек LD_PRELOAD.
CashCow

2
@Alex: У стандарті йдеться про невизначений, але як порядок посилання на практичне питання (зазвичай, залежно від компілятора), передбачено порядок ініціалізації.
ThomasMcLeod

1
@Thomas: Я, безумовно, навіть не віддалявся б на це покладатися. Я також точно не намагався б керувати системою збірки вручну.
Олександр К.

1
@ Алекс: вже не так важливо, але в той же час ми використовуємо порядок зв’язку для управління зображенням збірки, щоб зменшити кількість фізичних сторінок пам'яті. Є й інші побічні причини, за якими ви хочете контролювати порядок ініціалізації, навіть коли це не впливає на семантику програми, наприклад тестування порівняння продуктивності при запуску.
ThomasMcLeod

15

Ви також позначили це запитання як "C", тоді, строго кажучи про C, ваша ініціалізація повинна бути невдалою відповідно до розділу 6.7.8 "Ініціалізація" стандарту ISO C99.

Найбільш актуальним у цьому випадку видається обмеження №4, яке говорить:

Усі вирази в ініціалізаторі для об'єкта, який має статичну тривалість зберігання, повинні бути постійними виразами або рядковими літералами.

Отже, відповідь на ваше запитання полягає в тому, що код не відповідає стандарту C.

Ви, ймовірно, захочете видалити тег "C", якби вас цікавив лише стандарт C ++.


4
@ Remo.D, чи можете ви сказати нам, що знаходиться в цьому розділі. Не всі з нас мають стандарт С :).
UmmaGumma

2
Оскільки ви настільки вибагливі: На жаль, ANSI C є застарілим з 1989 року. ISO C90 або C99 є відповідними стандартами, які цитують.
Лундін

@Lundin: Ніхто ніколи не є досить прискіпливим :) Я читав ISO C99, але я впевнений, що це стосується і C90.
Ремо.Д

@Постріл. Ви маєте рацію, додали речення, яке, на мою думку, є найбільш актуальним тут.
Ремо.Д

3
@ Remo: +1 за надання інформації, що це неправда C; я цього не знав. Побачте, як люди навчаються, іноді за планом, іноді випадково!
Наваз

10

У розділі 3.6 в цілому дуже чітко про взаємодію mainта динамічні ініціалізації. "Позначений початок програми" не використовується більше ніде і є лише описом загальних намірів main(). Немає сенсу тлумачити цю фразу в нормативному порядку, що суперечить більш детальним і чітким вимогам Стандарту.


9

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

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

Стандарти передбачають, що ці змінні ініціалізуються за допомогою "магії", оскільки вони не говорять, як їх слід встановити перед ініціалізацією програми. Я думаю, що вони розглядали це як щось, що виходить за межі стандарту мови програмування.

Редагувати: Див., Наприклад, ISO 9899: 1999 5.1.2:

Усі об'єкти зі статичною тривалістю зберігання повинні бути ініціалізовані (встановлені на їх початкові значення) перед запуском програми. Спосіб та терміни такої ініціалізації інакше не визначені.

Теорія, за якою потрібно було зробити цю "магію", йде ще до народження С, коли це була мова програмування, призначена для використання лише для ОС UNIX, на комп'ютерах на базі оперативної пам'яті. Теоретично програма змогла б завантажити всі попередньо ініціалізовані дані з виконуваного файлу в оперативну пам’ять одночасно з тим, що сама програма була завантажена в оперативну пам'ять.

Відтоді комп'ютери та ОС розвивалися, і C використовується в набагато більш широкій області, ніж передбачалося спочатку. Сучасна комп'ютерна ОС має віртуальні адреси тощо, і всі вбудовані системи виконують код з ПЗУ, а не ОЗУ. Тож існує багато ситуацій, коли оперативну пам'ять неможливо встановити "автоматично".

Крім того, стандарт є надто абстрактним, щоб знати що-небудь про стеки та оперативну пам'ять тощо. Ці речі також потрібно зробити перед запуском програми.

Тому майже кожна програма C / C ++ має деякий код init / "copy-down", який виконується до виклику main, щоб відповідати правилам ініціалізації стандартів.

Наприклад, вбудовані системи зазвичай мають опцію під назвою "невідповідний ISO-запуск", коли вся фаза ініціалізації пропускається з міркувань продуктивності, а потім код фактично починається безпосередньо з основного. Але такі системи не відповідають стандартам, тому що ви не можете покластися на значення init глобальних / статичних змінних.


4

Ваша "програма" просто повертає значення з глобальної змінної. Все інше - код ініціалізації. Таким чином, стандарт дотримується - у вас просто дуже тривіальна програма і більш складна ініціалізація.



2

Схоже, англійська семантика, що посперечається. ОП називає його блок коду спочатку як "код", а пізніше як "програма". Користувач записує код, а потім компілятор пише програму.


1

main називається після ініціалізації всіх глобальних змінних.

Що стандарт не визначає, це порядок ініціалізації всіх глобальних змінних усіх модулів та статично пов'язаних бібліотек.


0

Так, головна - "точка входу" для кожної програми C ++, за винятком розширень, що стосуються конкретної реалізації. Незважаючи на це, деякі речі трапляються до основної, зокрема глобальної ініціалізації, наприклад, для main_ret.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.