INTRO
Мене засміяли коли я поцікавився які форми декларації головної функції main
допустимі.
Ніхто мені не дав відповіді, яку я хотів отримати. Тож це мої нотатки про головну функцію
My name is main
main
return
В пошуках ішформації про функцію main
мені на форумах та в групах понакидали лінки на різні статті в яких писалося про головну функцію програми. Так от в одній з цих статей автор писав, що писати у функціях які повертають void
писати return
не обов’язково. Дозволю собі не погодитися з автором. Писати це ключове слово потрібно і бажано.
return;
Писати чи не писати return
у void
функціях? Візьмемо для прикладу дві реалізації однієї і тієї ж функції. Одні з ключовим словом
void F(void){
// ...
// багато коду
// ...
return;
}
, а інша без
void F(void){
// ...
// багато коду
// ...
}
Якщо потікфункції лінійний то між функціями нема ніякої різниці. І компілятор навідь ворнінга не викине. Так як у void
функціях компілятор самостійно робить вихід з return
перед закриваючою скобкою }
, яка позначає кінець тіла функції.
Але якщо логіка функції не є лінійною то за допомогою ключового слова return
ми можемо достроково завершити роботу функції, якщо буде виконана чи не виконана якась умова.
void F(void){
// підготовка
// код
if(!ready)
return; // не треба продовжувати роботи
// ...
// багато коду
// ...
return; // успішне завершення
}
Я, наприклад, прихильник писати return
у void
функціях і можу не використати це ключове слово лише коли конкретно профілонив (не захтів писати).
Але як дізнатись чи успішно завершена функція чи аварійно? Потрібно якийсь результат отримати від її роботи
return 0;
Який результат має повертати int main(void)
?
Згідно стандарту нуль вказує на успішене виконання функції. У бібліотеці є визначений макрос який позначає EXIT_SUCCESS
успішну роботу. Цей макрос рівний 0
.
#include <stdlib.h>
int main(void){
// ...
// багато коду
// ...
return EXIT_SUCCESS; // успішне завершення
}
що тотожно
#include <stdlib.h>
int main(void){
// ...
// багато коду
// ...
return 0; // успішне завершення
}
А яке чило повертати, якщо було аварійне завершення програми? Стандартно повертають 1
.
#include <stdlib.h>
int main(void){
// ...
// багато коду
// ...
if(error)
return EXIT_FAILURE;
// ...
// багато коду
// ...
return EXIT_SUCCESS; // успішне завершення
}
це ж те саме
#include <stdlib.h>
int main(void){
// ...
// багато коду
// ...
if(error)
return 1;
// ...
// багато коду
// ...
return 0; // успішне завершення
}
Але ніде не написано, що обов’язково то має бути одиничка. Нам доступний широкий діапазон
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
Таким чином ми можемо передавати повідомлення для скрипта, що викликатиме нашу програму, для прикладу, і сповіщатимемо, як ми виконали роботу.
Ось для прикладу коди виходу 7zip
Код | Опис |
---|---|
0 | Немає помилок |
1 | Попередження (не фатальна помилка (и)).Наприклад, один або кілька файлів були заблоковані якоюсь іншою програмою, тому вони не стиснулися. |
2 | Критична помилка |
7 | Помилка командного рядка |
8 | Недостатьньо пам’яті для роботи |
255 | Користувач припинив процес |
Які можна ловити errorlevel
. Тому не обов’язково return 1;
можна писати будь яке число, але задокументувати чому таке, а не 1
чи -1
.
Ніби розібралися void
int
.
(void)
Ніби вже й розібралися який результат має повертати main
функція. Спробуємо розібратися, які параметри може отримувати головні функція.
Почнемо з найпростішого і будемо поступово підвищувати ставку.
Почнемо з int main(void)
Такий формат виклику не дозволяє налаштувати початкові дані роботи функції. Дані для обробки програма може отримувати в інтерактивному форматі під час робочого потоку. Влинути на режим роботи програми до її початку не можливо
int main(void){
return EXIT_SUCCESS; // успішне завершення
}
(int)
main
.int main(int count){
switch (count){
0:
// цей код ніколи не виконається так як не можливо запустити програму щоб count був рівним нулю!
break;
1:
// що робити при запуску без параметрів
break;
2:
// це робити коли параметр один
break;
3:
// ця дія якщо параметрів два
break;
// ...
break;
default:
// цей код запуститься в інших випадках
break;
}
return EXIT_SUCCESS;
}
Програма в такому вигляді працездатна, але дещо “кастрована”.
Ми може дізнатися кількість переданих у програму параметрів. Змінювати її поведінку у залежності від кількості аргументів, але всі ті аргументи… З програми дізнатися, що їй сказали нема можливості. Їй абсолютно “до лампочки” що написано у комнадному рядку.
На такий
myprog -h
, і такий
myprog --help
, і такий
myprog --version
програма реагуватиме однаково.
myprog foo bar
Програма може реагувати тільки на різну кількість параметрів. Це не найзручніший спосіб керування програмою.
Тож переходимо до наступного варіанту
(int, char**)
Наступна форма, яка є стандартизованою це
#include <stdlib.h>
int main(int int_value, char** char_pointer){
// ...
// багато коду
// ...
return 0; // успішне завершення
}
Перший параметр типу int
передає кількість переданих параметрів нашій програмі.
Цей параметр НІКОЛИ не може бути рівним нулю (0
). Змінна int_value
може мати значення в проміжку [1
…INT_MAX
].
Гадаю вам відомо, що в мовах C
та C++
, і в інших відомих мені мовах програмувння починають рахувати з нуля. Тобто під нульовим параметром ми маємо шлях до виконуваного вайлу.
А як ми можемо отримати значення передані програмі з командного рядка? Оскільки розглядаємо main
з двома параметрами то мабуть у другому параметрі. Другий параметр є вказівником на масив нультермінальних рядків з якого ми можемо прочитати всі параметри передані нашій програмі, першим елементом якого є шлях до виконуваного файлу.
#include <stdlib.h>
#include <stdio.h>
int main(int, char** pointer){
printf("My path: %s\n",*pointer);
return EXIT_SUCCESS; // успішне завершення
}
Цей коди виведе в консоль повідомлення з шляхом переданим у приграму при запиуску
|
|
виведе:
My path: ./a.out
А
$(pwd)/a.out
виведе повний шлях до виконуваного файлу 1
/home/Sam4uk.site/main/a.out
#include <stdlib.h>
#include <cstdio>
int main(int, char** pointer){
for(auto it{pointer}; *it!=nullptr; it++)
if (it == argv)
printf("Programm path or param[0]: %s\n", *it);
else
printf("\tparam [%d]: %s\n", it - pointer,*it);
return EXIT_SUCCESS; // успішне завершення
}
Якщо запустити програму
|
|
в результаті її роботи буде виведено приблизно такий текст
Programm path or param[0]: ./a.out
param [1]: one
param [2]: two
param [3]: foo
param [4]: bar
param [5]: end
Тобто не обов’язково звертатись до першого пареметру в якому передано їх кількість аргументів командного рядка.
#include <stdlib.h>
#include <cstdio>
int main(int count, char** pointer){
for(auto it{0}; it!=count; it++)
if (0 == it)
printf("Programm path or param[0]: %s\n", *pointer);
else
printf("\tparam [%d]: %s\n", it, pointer[it]);
return EXIT_SUCCESS; // успішне завершення
}
Цей код тотожний попередньому (результат виконання однаковий).
Тепер програма може отримувати та реагувати на різні параметри, та різну їх кількість. Лишається лише їх розпарсити але це вже інша тема
(int, char**, char**)
Йдемо далі? Додамо ще один “документований” параметр?
Мені траплялися фрагменти кодів, де main
має три параметри. Саме це мене підштовхуло шукати всі можливі та умовно можливі варіанти функції.
Якщо попередній варіант int main(int, char**)
ми могли прочитати кількість c-string
з першого параметру, то наступний третій параметр… Щоб отримати кількість елементів нам потрібно “вручну” рахувати
#include <сstdlib>
#include <cstdio>
int main(int, char**, char** e){
int i{0};
for(auto c{e}; *c != nullptr; c++,i++)
printf("\t%s \n",*c);
printf("total: %d", i);
return EXIT_SUCCESS; // успішне завершення
}
На цьому ноуті, який я використовую цей код нарахував 81
рядок змінних
GJS_DEBUG_TOPICS=JS ERROR;JS LOG
LANGUAGE=uk_UA
USER=sam4uk
LC_TIME=uk_UA.UTF-8
# ......................................................
# ......................................................
TERM=xterm-256color
P9K_TTY=old
_P9K_TTY=/dev/pts/2
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
total: 81
Деякі рядки я видалив (Не потрібно всім знати, яке сміття в моїй системі. Це моє сміття).
З вихлопу зрозуміло, що це змінні оточення. Цікаво а в динаміці можна їх читати?
Хочу підглянути адреси де знаходяться параметри.
#include <сstdlib>
#include <cstdio>
int main(int, char**, char** e){
printf("int @: %ld\nchar ** @:%ld\nchar ** @:%ld", &p1, p2, p3);
return EXIT_SUCCESS; // успішне завершення
}
І кілька разів поспіль запустимо виконуваний файл. Ми помітимо, що кожного разу адреси різні.
Тож ми отримуємо копію змінних середовища на момент пуску програми. Від моменту пуску до моменту закінчення роботи програми ці значення можуть змінитися. Актуальні значення змінних середовища потрібно отримувти іншими методами.
(…)
int main(...){}
Я не знаю як з таким кодом працювати, але оце компілюється.
Компілюється з ворнінгом, але ще й запускається.
Мені ще є що вчити. Одже продовження буде.
Ставлю трикрапик тут, а не в кінці
Замість резюме
Я довідався про стандартні 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** | Змінні оточнення |
Також дозволено використовувати фіктивну декларацію параметів (оголосити що функція отримуватиме параметр, але не оголосити ім’я змінної).
Про приклади
Не варто кидати помідорами та камінням за те як я написав приклади кодів. Десь я навмисне знущався зі стилю. Десь навмисне відходив від стандарту. Десь через неуважність. Десь через не знання. Я не намагаюсь когось навчити. Я сам вчусь.
Але якщо маєте слушні зауваження, я з радістю їх вислухаю.
Виведення дійсне для Linux систем ↩︎