MY NAME IS main
INTRO
Мене засміяли коли я поцікавився які форми декларації головної функції main допустимі.
Ніхто мені не дав відповіді, яку я хотів отримати. Тож це мої нотатки про головну функцію
My name is main
main
return
В пошуках інформації про функцію main мені на форумах та в групах понакидали лінки на різні статті в яких писалося про головну функцію програми. Так от в одній з цих статей автор писав, що писати у функціях які повертають void писати return не обов’язково. Дозволю собі не погодитися з автором. Писати це ключове слово потрібно і бажано.
return;
Писати чи не писати return у void функціях? Візьмемо для прикладу дві реалізації однієї і тієї ж функції. Одні з ключовим словом
, а інша без
Якщо потікфункції лінійний то між функціями нема ніякої різниці. І компілятор навідь ворнінга не викине. Так як у void функціях компілятор самостійно робить вихід з return перед закриваючою скобкою }, яка позначає кінець тіла функції.
Але якщо логіка функції не є лінійною то за допомогою ключового слова return ми можемо достроково завершити роботу функції, якщо буде виконана чи не виконана якась умова.
Я, наприклад, прихильник писати return у void функціях і можу не використати це ключове слово лише коли конкретно профілонив (не захтів писати).
Але як дізнатись чи успішно завершена функція чи аварійно? Потрібно якийсь результат отримати від її роботи
return 0;
Який результат має повертати int main(void)?
Згідно стандарту нуль вказує на успішене виконання функції. У бібліотеці є визначений макрос який позначає EXIT_SUCCESS успішну роботу. Цей макрос рівний 0.
що тотожно
А яке чило повертати, якщо було аварійне завершення програми? Стандартно повертають 1.
це ж те саме
Але ніде не написано, що обов’язково то має бути одиничка. Нам доступний широкий діапазон
| min | max | bit |
|---|---|---|
| -32768 | 32767 | 16 |
| -2,147,483,648 | 2,147,483,647 | 32 |
| -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | 64 |
Все окрім 0
Таким чином ми можемо передавати повідомлення для скрипта, що викликатиме нашу програму, для прикладу, і сповіщатимемо, як ми виконали роботу.
| Код | Опис |
|---|---|
| 0 | Немає помилок |
| 1 | Попередження (не фатальна помилка (и)).Наприклад, один або кілька файлів були заблоковані якоюсь іншою програмою, тому вони не стиснулися. |
| 2 | Критична помилка |
| 7 | Помилка командного рядка |
| 8 | Недостатьньо пам’яті для роботи |
| 255 | Користувач припинив процес |
Які можна ловити errorlevel. Тому не обов’язково return 1; можна писати будь яке число, але задокументувати чому таке, а не 1 чи -1.
Ніби розібралися void int.
(void)
Ніби вже й розібралися який результат має повертати main функція. Спробуємо розібратися, які параметри може отримувати головні функція.
Почнемо з найпростішого і будемо поступово підвищувати ставку.
Почнемо з int main(void)
Такий формат виклику не дозволяє налаштувати початкові дані роботи функції. Дані для обробки програма може отримувати в інтерактивному форматі під час робочого потоку. Влинути на режим роботи програми до її початку не можливо
(int)
Увага
Цей варіант виклику допустимий, але компілятор викидає попередження, що потрібно перевірити кількість аргументів у функції main.
Програма в такому вигляді працездатна, але дещо “кастрована”.
Ми може дізнатися кількість переданих у програму параметрів. Змінювати її поведінку у залежності від кількості аргументів, але всі ті аргументи… З програми дізнатися, що їй сказали нема можливості. Їй абсолютно “до лампочки” що написано у комнадному рядку.
На такий
, і такий
, і такий
програма реагуватиме однаково.
Програма може реагувати тільки на різну кількість параметрів. Це не найзручніший спосіб керування програмою.
Тож переходимо до наступного варіанту
(int, char**)
Наступна форма, яка є стандартизованою це
Перший параметр типу int передає кількість переданих параметрів нашій програмі.
Цей параметр НІКОЛИ не може бути рівним нулю (0). Змінна int_value може мати значення в проміжку [1…INT_MAX].
Гадаю вам відомо, що в мовах C та C++, і в інших відомих мені мовах програмувння починають рахувати з нуля. Тобто під нульовим параметром ми маємо шлях до виконуваного вайлу.
А як ми можемо отримати значення передані програмі з командного рядка? Оскільки розглядаємо main з двома параметрами то мабуть у другому параметрі. Другий параметр є вказівником на масив нультермінальних рядків з якого ми можемо прочитати всі параметри передані нашій програмі, першим елементом якого є шлях до виконуваного файлу.
Цей коди виведе в консоль повідомлення з шляхом переданим у приграму при запиуску
виведе:
А
виведе повний шлях до виконуваного файлу 1
Якщо запустити програму
в результаті її роботи буде виведено приблизно такий текст
Тобто не обов’язково звертатись до першого пареметру в якому передано їх кількість аргументів командного рядка.
Цей код тотожний попередньому (результат виконання однаковий).
Тепер програма може отримувати та реагувати на різні параметри, та різну їх кількість. Лишається лише їх розпарсити але це вже інша тема
(int, char**, char**)
Йдемо далі? Додамо ще один “документований” параметр?
Мені траплялися фрагменти кодів, де main має три параметри. Саме це мене підштовхуло шукати всі можливі та умовно можливі варіанти функції.
Якщо попередній варіант int main(int, char**) ми могли прочитати кількість c-string з першого параметру, то наступний третій параметр… Щоб отримати кількість елементів нам потрібно “вручну” рахувати
На цьому ноуті, який я використовую цей код нарахував 81 рядок змінних
Деякі рядки я видалив (Не потрібно всім знати, яке сміття в моїй системі. Це моє сміття).
З вихлопу зрозуміло, що це змінні оточення. Цікаво а в динаміці можна їх читати? Хочу підглянути адреси де знаходяться параметри.
І кілька разів поспіль запустимо виконуваний файл. Ми помітимо, що кожного разу адреси різні. Тож ми отримуємо копію змінних середовища на момент пуску програми. Від моменту пуску до моменту закінчення роботи програми ці значення можуть змінитися. Актуальні значення змінних середовища потрібно отримувти іншими методами.
(…)
Я не знаю як з таким кодом працювати, але оце компілюється. Компілюється з ворнінгом, але ще й запускається.
Мені ще є що вчити. Одже продовження буде.
Ставлю трикрапик тут, а не в кінці
Замість резюме
Я довідався про стандартні mainи.
int main()int main(void)int main(int, char**)int main(int, char**, char**)
І про (хочеться написати слово нестандартні, але за це можна й отрмати гнилими помідорами по пиці) ось такі варіанти. На ці варіанти компілятор матюкається, але все ж збирає життєздатні бінарні файли
int main(int)int main(int, ...)int main(int, char**, ...)int main(int, char**, char**, ...)
Те що компілятор дозволяє “скріпя зубами” зібрати згадані мейни не значить що їх треба використовувати. Синтаксис мови дозволяє так писати, а бездушний компілятор попереджає, що за свої діяння він не несе відповідальності - його попросили так зробити.
Про імена
Історично склалось, що параметри функції main мають наступні імена
| # | name | type | |
|---|---|---|---|
| 1. | argc | int | Кількість параметрів передених з командного рядка |
| 2. | argv | char** | Параметри передані через командний рядок |
| 3. | env | char** | Змінні оточнення |
Також дозволено використовувати фіктивну декларацію параметів (оголосити що функція отримуватиме параметр, але не оголосити ім’я змінної).
Про приклади
Не варто кидати помідорами та камінням за те як я написав приклади кодів. Десь я навмисне знущався зі стилю. Десь навмисне відходив від стандарту. Десь через неуважність. Десь через не знання. Я не намагаюсь когось навчити. Я сам вчусь.
Але якщо маєте слушні зауваження, я з радістю їх вислухаю.
BONUS
Коза на всі випадки життя.
Виведення дійсне для Linux систем ↩︎