Ця сторінка виглядає краще якщо JavaScript працює

MY NAME IS main

 ·   ·  ☕ 8 хв читати

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; // успішне завершення
  }

Але ніде не написано, що обов’язково то має бути одиничка. Нам доступний широкий діапазон

minmaxbit
-327683276716
-2,147,483,6482,147,483,64732
-9,223,372,036,854,775,8089,223,372,036,854,775,80764

Все окрім 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)

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 може мати значення в проміжку [1INT_MAX].

Гадаю вам відомо, що в мовах C та C++, і в інших відомих мені мовах програмувння починають рахувати з нуля. Тобто під нульовим параметром ми маємо шлях до виконуваного вайлу.

А як ми можемо отримати значення передані програмі з командного рядка? Оскільки розглядаємо main з двома параметрами то мабуть у другому параметрі. Другий параметр є вказівником на масив нультермінальних рядків з якого ми можемо прочитати всі параметри передані нашій програмі, першим елементом якого є шлях до виконуваного файлу.


  #include <stdlib.h>
  #include <stdio.h>

int main(int, char** pointer){
  printf("My path: %s\n",*pointer);
  return EXIT_SUCCESS; // успішне завершення
  }

Цей коди виведе в консоль повідомлення з шляхом переданим у приграму при запиуску

1
./a.out

виведе:

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; // успішне завершення
  }

Якщо запустити програму

1
./a.out one two foo bar end

в результаті її роботи буде виведено приблизно такий текст

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и.

  1. int main() int main(void)
  2. int main(int, char**)
  3. int main(int, char**, char**)

І про (хочеться написати слово нестандартні, але за це можна й отрмати гнилими помідорами по пиці) ось такі варіанти. На ці варіанти компілятор матюкається, але все ж збирає життєздатні бінарні файли

  1. int main(int)
  2. int main(int, ...)
  3. int main(int, char**, ...)
  4. int main(int, char**, char**, ...)

Те що компілятор дозволяє “скріпя зубами” зібрати згадані мейни не значить що їх треба використовувати. Синтаксис мови дозволяє так писати, а бездушний компілятор попереджає, що за свої діяння він не несе відповідальності - його попросили так зробити.

Про імена

Історично склалось, що параметри функції main мають наступні імена

#nametype
1.argcintКількість параметрів передених з командного рядка
2.argvchar**Параметри передані через командний рядок
3.envchar**Змінні оточнення

Також дозволено використовувати фіктивну декларацію параметів (оголосити що функція отримуватиме параметр, але не оголосити ім’я змінної).

Про приклади

Не варто кидати помідорами та камінням за те як я написав приклади кодів. Десь я навмисне знущався зі стилю. Десь навмисне відходив від стандарту. Десь через неуважність. Десь через не знання. Я не намагаюсь когось навчити. Я сам вчусь.

Але якщо маєте слушні зауваження, я з радістю їх вислухаю.


  1. Виведення дійсне для Linux систем ↩︎

Поширити в
Підтримайте автора

Sam4uk
НАПИСАВ
Sam4uk
Embedded Software Engineare C/C++