Что такое main в си. Несколько подробностей о функции main
Минимальной программой на C++ является
Int main() { } // the minimal C++ program
В этой программе представлено объявление функции main, которая не принимает никаких аргументов. Фигурные скобки отражают группировку в C++ и в данном случае показывают тело функции main. То есть начало функции main - открывающая скобка, и конец функции main - закрывающая скобка. Двойной слэш показывает начало комментария. Комментарии игнорируются компилятором и служат для уточнения информации в коде.
Каждая программа, написанная на C++, имеет в себе функцию main() , с которой начинается запуск программы. Функция main(), как правило, возвращает результат своего выполнения, о чем сигнализирует int (integer - целочисленный), который написан перед функцией main() . При правильном, успешном завершении функция main() возвращает в качестве результата 0 . Значение результата, отличное от нуля сигнализирует о нештатном завершении программы.
Возвращаемое программой значение по завершению может использоваться в операционной системе для служебных целей.
Типичным примером первой программы на любом языке программирования является вывод текста "Hello, World!":
#include
Но так ли всё просто в данной программе? В целом, уже одна эта маленькая программа несёт в себе очень большой пласт информации, который необходимо понимать для разработки на C++.
- Директива
#include
#include
сообщает компилятору о том, что необходимо подключить некий заголовочный файл, компоненты которого планируется использовать в файле, где объявлена функция main() . iostream - это стандартная библиотека ввода вывода из STL. То есть здесь уже используется функционал библиотек, хоть и являющихся для языка стандартом. И последний момент - это угловые скобки, в которых находится название библиотеки, которые говорят о том, что это включение внешних файлов в проект, а не тех которые находятся в составе проекта. Те же файлы, которые находятся в составе проекта подключаются обрамляясь в обычные кавычки, например #include "myclass.h". Такое подключение библиотек является стандартом. Например, в Visual Studio при несоблюдении данного стандарта будут выпадать ошибки.
std - это использование пространства имён, в котором находится оператор вывода cout. Пространства имён были введены в C++ для того, чтобы убрать конфликты имён между библиотеками и проектом разработчика, если где-то имеются повторяющиеся наименования функций или классов. В Java для разрешения конфликтов имён используется система пакетов.
cout - это оператор вывода, у которого перегружен оператор << , чтобы не использовать отдельную функцию для вывода текста в консоль.
Это помимо того, что запись функции main может иметь различный вид, хотя стандартом являются две записи:
- int main()
- int main(int argc, char* argv)
Можно встретить ещё записи типа void main() и т.д. Но это ошибочные записи, хотя в некоторых компиляторах они будут компилироваться, причём даже без ошибок и предупреждений.
В записи int main(int argc, char* argv) передаются аргументы:
- argc - указывает количество переданных аргументов. Всегда не меньше 1, поскольку всегда передаётся имя программы
- argv - массив указателей на аргументы, которые передаются в качестве строковых переменных.
Если argc больше 1, значит при запуске программы были переданы дополнительные аргументы.
Проверка может выглядеть следующим образом:
#include В целом, есть большое количество моментов, которые необходимо понимать в C++ даже для небольшой программы, но от этого только интереснее;-) Однажды заинтересовался, содержимым стека функции main процесса в linux. Провел некоторые изыскания и теперь представляю вам результат. Варианты описания функции main: Argc - число параметров Размер сегмента стека можно глянуть в файле maps: Перед тем, как загрузчик передаст управление в main, он инициализирует содержимое массивов параметров командной строки, переменных окружения, вспомогательный вектор. Для 32 битов не проверял, но скорее всего достаточно только разделить размеры на два. 1. Обращение к адресам, выше верхней точки, вызывает Segfault. Вспомогательный вектор Второй способ получить содержимое вектора: Самый удобочитаемое представление получается установкой переменной окружения LD_SHOW_AUXV. LD_SHOW_AUXV=1 ls Возвращение из main() Надеюсь, было интересно. Спасибо пользователю Xeor за полезную наводку. Borland С++ поддерживает три аргумента main(). Первые два - это традиционные argc и argv. Это единственные аргументы функции main(), определяемые стандартом ANSI С. Они позволяют передавать аргументы командной строки в программу. Аргументы командной строки - это информация, следующая за именем программы в командной строке операционной системы. Например, когда программа компилируется с помощью строчного компилятора Borland, набирается, как правило, bcc имя_ программы
Где имя_программы
- это программа, которую необходимо откомпилировать. Имя программы передается компилятору в качестве аргумента. Параметр argc содержит число аргументов командной строки и является целым числом. Он всегда равен, по крайней мере, 1, поскольку имя программы квалифицируется как первый аргумент. Параметр argv - это указатель на массив символьных указателей. Каждый элемент данного массива указывает на аргумент командной строки. Все аргументы командной строки - это строки. Все числа конвертируются программой во внутренний формат. Следующая программа выводит «Hello», а затем имя пользователя, если его набрать прямо за именем программы:
#include Если назвать данную программу name, а имя пользователя Сергей, то для запуска программы следует набрать: Аргументы командной строки должны отделяться пробелами или табуляциями. Запятые, точки с запятыми и им подобные символы не рассматриваются как разделители. Например: Состоит из трех строк, в то время как Herb,Rick,Fred Это одна строка - запятые не являются разделителями. Если необходимо передать строку, содержащую пробелы или табуляции в виде одного аргумента, следует ее заключить в двойные кавычки. Например, это один аргумент:
"this is a test" Важно правильно объявить argv. Наиболее типичным методом является: Пустые скобки указывают на то, что массив не имеет фиксированной длины. Можно получить доступ к отдельным элементам с помощью индексации argv. Например, argv указывает на первую строку, всегда содержащую имя программы. argv указывает на следующую строку и так далее. Ниже приведен небольшой пример по использованию аргументов командной строки. Он отсчитывает в обратном порядке от значения, указанного в командной строке, и при достижении нуля подает сигнал. Обратим внимание, что первый аргумент содержит число, преобразованное в целое число с использованием стандартной функции atoi(). Если в качестве второго аргумента присутствует строка "display", то на экране будет отображаться сам счетчик.
/* программа отсчета */
#include Обратим внимание, что если не указаны аргументы, появляется сообщение об ошибке. Это наиболее типично для программ, использующих аргументы командной строки для выдачи инструкций, если была попытка запустить программу без правильной информации. Для доступа к отдельным символам командной строки следует добавить второй индекс к argv. Например, следующая программа выводит все аргументы, с которыми она вызывалась, по одному символу за раз:
#include Надо помнить, что первый индекс предназначен для доступа к строке, а второй - для доступа к символу строки. Обычно argc и argv используются для получения исходных команд. Теоретически можно иметь до 32767 аргументов, но большинство операционных систем не позволяют даже близко подойти к этому. Обычно данные аргументы используются для указания имени файла или опций. Использование аргументов командной строки придает программе профессиональный вид и допускает использование программы в командных файлах. Если подсоединить файл WILDARGS.OBJ, поставляемый с Borland С++, то можно будет использовать шаблоны в аргументах типа *.EXE. (Borland С++ автоматически обрабатывает шаблоны и соответствующим образом увеличивает argc.) Например, если подсоединить к следующей программе WILDARGS.OBJ, она выдаст, сколько файлов соответствует имени указанного в командной строке файла:
/* Скомпонуйте данную программу с WILDARGS.OBJ */
#include Если назвать данную программу WA, затем запустить ее как указано ниже, получим число файлов, имеющих расширение ЕХE, и список имен этих файлов: Помимо argc и argv Borland С++ также предоставляет третий аргумент командной строки -env. Параметр env позволяет программе получить доступ к информации о среде операционной системы. Параметр env должен следовать за argc и argv и объявляется следующим образом: Как можно видеть, env объявляется так же, как и argv. Так же, как и argv, это указатель на массив строк. Каждая строка - это строка среды, определенная операционной системой. Параметр env не имеет аналога параметра argc, который сообщал бы, сколько имеется строк среды. Вместо этого последняя строка среды нулевая. Следующая программа выводит все строки среды, определенные на текущий момент в операционной системе:
/* данная программа выводит все строки окружения */
#include Обратим внимание, что хотя argc и argv не используются программой, они должны присутствовать в списке параметров. С не знает имена параметров. Вместо этого их использование определяется по порядку объявления параметров. Фактически можно обозвать параметр как угодно. Поскольку argc, argv и env - это традиционные имена, то лучше их использовать и далее, чтобы любой человек, читающий программу, мог мгновенно понять, что это аргументы функции main(). Для программ типичной задачей является поиск значения, определенного в строке среды. Например, содержимое строки PATH позволяет программам использовать пути поиска. Следующая программа демонстрирует, как найти строки, объявляющие стандартные пути поиска. Она использует стандартную библиотечную функцию strstr(), имеющую следующий прототип: Char *strstr(const char *str1, const char *str2); Функция strstr() ищет строку, на которую указывает str1 в строке, на которую указывает str2. Если такая строка найдена, то возвращается указатель на первое положение. Если не найдено соответствий, то функция возвращает NULL.
/* программа ищет среди строк окружения строку, содержащую PATH */
#include При автоматизированном создании консольного приложения в языке программирования С++, автоматически создается главная функция очень похожая на эту: int
main(int
argc, char
* argv) Заголовок функции содержит сигнатуру главной функции main()
с аргументами argс
и argv
. При запуске программы через командную строку Windows можно передавать ей некоторую информацию. При этом командная строка будет иметь вид: Аргументы командной строки разделяются одним или несколькими пробелами. Аргумент argv
содержит полное имя приложения: #include
cout << argv << endl; Return
0; Результат выполнения Пример
: вычисление произведения двух целых чисел #include
Int
a = 0, b=0; If
(argc > 1) a = StrToInt(argv); If
(argc > 2) b = StrToInt(argv); cout << a <<«*»
<< b << «= «
<< a*b << endl; Return
0; Запуск программы осуществляется как Результат выполнения Для передачи аргументов командной строки при отладке программы необходимо обратиться к меню Свойства
проекта. При запуске программы в режиме отладки введенные аргументы будут восприниматься программой как аргументы командной строки.
Основная форма записи функции имеет следующий вид В версии C89, если тип данных возвращаемый функцией, явно не задан, то подразумевается тип int
. В языке C++ и версии C99, тип int
по умолчанию, не поддерживается, хотя в большинстве компиляторов C++ такое предположение остаётся в силе. В языке C++ все функции должны иметь прототипы, а в языке C прототипы формально необязательны, но весьма желательны. Общая форма определения прототипа имеет следующий вид. В языке C для задания прототипа функции, не имеющей параметров, вместо списка параметров используется ключевое слово void
. В языке C++ пустой список параметров в прототипе функция означает, что функция на имеет параметров. Слово void
в этом случае необязательно. Возврат значений в функции осуществляется с помощью оператора return
. он имеет две формы записи. Return;
return значение
;
В языке C99 и C++ форма оператора return
, которая не задаёт возвращаемого значения, должна использоваться только в void
-функциях. В языке C++ функции могут перегружаться
. Когда говорят, что функция перегружена, это означает, что две или более функций имеют одинаковые имена, однако все версии перегруженных функций имеют различное количество или тип параметров. Рассмотрим пример следующие три перегруженные функции. Void func (int a){
cout
В каждом случае для определения того, какая версия функции func()
будет вызвана, анализируется тип и количество аргументов. В языке C++ параметру функции можно присвоить значение по умолчанию, которое автоматически будет использовано в том случае, если при вызове функции соответствующий аргумент не будет задан. Например. Void func (int a = 0, int b = 10){}
//вызов
func();
func(-1);
func(-1, 99);
В языках C и C++ определенны правила видимости, которые устанавливают такие понятия как область видимости и время жизни объектов. Различают глобальную область видимости и локальную. Глобальная
область видимости существует вне всех других областей. Имя объявленное в глобальной области, известно всей программе. Например глобальная переменная доступна для использования всеми функциями программы. Глобальные переменные существуют на протяжении всего жизненного цикла программы. Локальная
область видимости определяется границами блока. Имя объявленное внутри локальной области,известно только внутри этой области. Локальные переменные создаются при входе в блок и разрушаются при выходе из него. Это означает, что локальные переменные не хранят своих значений между вызовами функций. Чтобы сохранить значения переменных между вызовами, можно использовать модификатор static
. В языках C и C++ функции могут вызывать сами себя. Этот процесс называют рекурсией
, а функцию, которая сама себя вызывает - рекурсивной. В качестве примера приведём функцию fact(), вычисляющую факториал целого числа. Int fact (int n)
{
int ans;
if (n == 1) return 1;
ans = fact (n-1) * n;
return ans;
}
Выполнение C/C++ программы начинается с выполнения функции main()
. (Windows-программы вызывают функцию WinMain()); Функция main()
не имеет прототипа. Следовательно, можно использовать различные формы функции main()
. Как для языка C, так и для языка C++ допустимы следующие варианты функции main()
. Int main();
int main(int argc, char *argv)
Как видно из второй формы записи, ф. main()
поддерживает по крайней мере два параметра. Они называются argc
и argv
. Эти аргументы будут хранить количество аргументов командной строки и указатель на них соответственно. Параметр argc
имеет целый тип, и его значение всегда будет не меньше числа 1, поскольку как предусмотрено в языках C и C++, первым аргументом является всегда имя программы. Параметр argv
должен быть объявлен как массив символьных указателей, в которых каждый элемент указывает на аргумент командной строки. Ниже приведён пример программы, в которой демонстрируется использование этих аргументов. #include Несмотря на то что в языках C и С++ по умолчанию используется передача параметров по значению, можно вручную построить вызов ф. с передачей параметров по ссылке. Для этого нужно аргументу передать указатель. Поскольку в таком случае функции передаётся адрес аргумента, оказывается возможным изменять значение аргумента вне функции
. Например. Void swap (int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
//вызов
swap (&a, &b);
В языке C++ адрес переменной можно передать функции автоматически. Это реализуется с помощью параметра-ссылки
. При использовании параметра-ссылки функции передаётся адрес аргумента, и функция работает с аргументом, а не с копией. Чтобы создать параметр-ссылку необходимо перед его именем поставить символ "амперсанда" (&). Внутри ф. этот параметр можно использовать обычным образом, не прибегая к оператору "звёздочка" (*), например. Void swap (int &x, int &y){
int temp;
temp = x;
x = y;
y = temp;
}
//вызов
swap (a, b);
В языке C++ определенны три спецификатора функций: Спецификатор inline
представляет собой обращённое к компилятору требование: вместо создания вызова функции раскрыть её код прямо в строке. Если компилятор не может вставить функцию в строку он имеет право проигнорировать это требование. Спецификатором inline
могут определяться как функции-члены, так и не функции-члены. В качестве виртуальной функции (с помощью спецификатора virual
) ф. определяется в базовом классе и переопределяется в производным классом. На примере виртуальных функций можно понять, как язык C++ поддерживает полиморфизм. Спецификатор explicit
применяется только к конструкторам, Конструктор, определённый как explicit
, будет задействован только в том случае, когда инициализация в точности соответствует тому, что задано конструктором. Никаких преобразований выполняться не будет (т.е. спецификатор explicit
создаёт "неконвертирующий конструктор"). Общая форма определения шаблонной функции имеет следующий вид. Tetemplate тип> тип_возврата имя_функции
(список_параметров
)
{
//тело функции
}
Здесь тип
означает метку - заполнитель для типа данных, с которыми эта функция фактически будет иметь дело. В операторе template
можно определить несколько параметров-типов данных, используя форму списка элементов, разделённых запятыми.
Рассмотрим пример. Template На функцию, как и на любой другой объект языка C, Можно создать указатель. Синтаксис следующий. Функцию с помощью указателя можно вызвать следующим образом. Следующий пример демонстрирует создание указателя и и два способа вызова функции через указатель, а также передачу функции как параметра другим функциям. Double y;
double (*p)(doublr x);
p=sin; //создание указателя на функцию sin()
y = (*p)(2.5); //вызов
y = p(2.5); //вызов
//передача функции как параметра
double y;
double f(double (*c)(double x), double y){
return c(y); //вызываем переданную функцию в указатель c и возвращаем значение
}
y = f(sin, 2.5); //передаём функции f функцию sin, а также параметр который будет обрабатываться
Создавать массивы указателей на функции так же можно. Int f1(void);
int f2(void);
int f3(void);
int (*p)(void) = {f1, f2, f3}
y = (*p)(); //вызов функции f2
y = p(); //вызов функции f3
1. int main()
2. int main(int argc, char **argv)
3. int main(int argc, char **argv, char **env)
4. int main(int argc, char **argv, char **env, ElfW(auxv_t) auxv)
5. int main(int argc, char **argv, char **env, char **apple)
argv - нуль-терминальный массив указателей на строки параметров командной строки
env - нуль-терминальный массив указателей на строки переменных окружения. Каждая строка в формате ИМЯ=ЗНАЧЕНИЕ
auxv - массив вспомогательных значение (доступно только для PowerPC )
apple - путь к исполняемому файлу (в MacOS и Darwin )
Вспомогательный вектор - массив с различной дополнительной информацией, такой как эффективный идентификатор пользователя, признак setuid бита, размер страницы памяти и т.п.
cat /proc/10918/maps
…
7ffffffa3000-7ffffffff000 rw-p 00000000 00:00 0
…
После инициализации верхняя часть стека выглядит примерно так, для 64битной версии.
Старший адрес сверху.1.
0x7ffffffff000
Верхняя точка сегмента стека. Обращение вызывает segfault
0x7ffffffff0f8
NULL
void*
8
0x00"
2.
filename
char
1+
«/tmp/a.out»
char
1
0x00
...
env
char
1
0x00
...
char
1
0x00
3.
0x7fffffffe5e0
env
char
1
..
char
1
0x00
...
argv
char
1
0x00
...
char
1
0x00
4.
0x7fffffffe5be
argv
char
1+
«/tmp/a.out»
5.
Массив случайной длины
6.
данные для auxv
void*
48"
AT_NULL
Elf64_auxv_t
16
{0,0}
...
auxv
Elf64_auxv_t
16
7.
auxv
Elf64_auxv_t
16
Ex.: {0x0e,0x3e8}
NULL
void*
8
0x00
...
env
char*
8
8.
0x7fffffffe308
env
char*
8
0x7fffffffe5e0
NULL
void*
8
0x00
...
argv
char*
8
9.
0x7fffffffe2f8
argv
char*
8
0x7fffffffe5be
10.
0x7fffffffe2f0
argc
long int
8"
число аргументов + 1
11.
Локальные переменные и аргументы, функций вызываемых до main
12.
Локальные переменные main
13.
0x7fffffffe1fc
argc
int
4
число аргументов + 1
0x7fffffffe1f0
argv
char**
8
0x7fffffffe2f8
0x7fffffffe1e8
env
char**
8
0x7fffffffe308
14.
Переменные локальных функций
" - описания полей в документах не нашел, но в дампе явно видны.
2. Строка, содержащая путь к исполняемому файлу.
3. Массив строк с переменными окружения
4. Массив строк с параметрами командной строки
5. Массив случайной длинны. Его выделение можно отключить командами
sysctl -w kernel.randomize_va_space=0
echo 0 > /proc/sys/kernel/randomize_va_space
6. Данные для вспомогательного вектора (например строка «x86_64»)
7. Вспомогательный вектор. Подробнее ниже.
8. Нуль-терминальный массив указателей на строки переменных окружения
9. Нуль-терминальный массив указателей на строки параметров командной строки
10.Машинное слово, содержащее число параметров командной строки (один из аргументов «старших» функций см. п. 11)
11.Локальные переменные и аргументы, функций вызываемых до main(_start,__libc_start_main..)
12.Переменные, объявленные в main
13.Аргументы функции main
14.Переменные и аргументы локальных функций.
Для i386 и x86_64 нельзя получить адрес первого элемента вспомогательного вектора, однако содержимое этого вектора можно получить другими способами. Один из них - обратиться к области памяти, лежащей сразу за массивом указателей на строки переменных окружения.
Это должно выглядеть примерно так:
#include
Структуры Elf{32,64}_auxv_t описаны в /usr/include/elf.h. Функции заполнения структур в linux-kernel/fs/binfmt_elf.c
hexdump /proc/self/auxv
AT_HWCAP: bfebfbff //возможности процессора
AT_PAGESZ: 4096 //размер страницы памяти
AT_CLKTCK: 100 //частота обновления times()
AT_PHDR: 0x400040 //информация о заголовке
AT_PHENT: 56
AT_PHNUM: 9
AT_BASE: 0x7fd00b5bc000 //адрес интерпретатора, то бишь ld.so
AT_FLAGS: 0x0
AT_ENTRY: 0x402490 //точка входа в программу
AT_UID: 1000 //идентификаторы пользователя и группы
AT_EUID: 1000 //номинальные и эффективные
AT_GID: 1000
AT_EGID: 1000
AT_SECURE: 0 //поднят ли setuid флаг
AT_RANDOM: 0x7fff30bdc809 //адрес 16 случайных байт,
генерируемых при запуске
AT_SYSINFO_EHDR: 0x7fff30bff000 //указатель на страницу, используемую для
//системных вызовов
AT_EXECFN: /bin/ls
AT_PLATFORM: x86_64
Слева - название переменной, справа значение. Все возможные названия переменных и их описание можно глянуть в файле elf.h. (константы с префиксом AT_)
После инициализации контекста процесса управление передается не в main(), а в функцию _start().
main() вызывает уже из __libc_start_main. Эта последняя функция имеет интересную особенность - ей передается указатель на функцию, которая должна быть выполнена после main(). И указатель этот передается естественно через стек.
Вообще аргументы __libc_start_main имеют вид, согласно файла glibc-2.11/sysdeps/ia64/elf/start.S
/*
* Arguments for __libc_start_main:
* out0: main
* out1: argc
* out2: argv
* out3: init
* out4: fini //функция вызываемая после main
* out5: rtld_fini
* out6: stack_end
*/
Т.е. чтобы получить адрес указателя fini нужно сместиться на два машинных слова от последней локальной переменной main.
Вот что получилось(работоспособность зависит от версии компилятора):
#include
Удач.
{
if(argc!=2)
{
printf ("You forgot to type your name\n");
return 1;
}
printf("Hello %s", argv);
return 0;
}
name Сергей.
В результате работы программы появится:
«Hello Сергей».
#include
# include
int main(int argc, char *argv)
{
int disp, count;
if(argc<2)
{
printf("You must enter the length of the count\n");
printf ("on the command line. Try again.\n");
return 1;
}
if (argc==3 && !strcmp(argv,"display")) disp = 1;
else disp = 0;
for(count=atoi(argv); count; -count)
if (disp) printf("%d ", count);
printf("%c", "\a"); /* на большинстве компьютеров это звонок */
return 0;
}
int main(int argc, char *argv)
{
int t, i;
for(t=0; t
i = 0;
while(argv[t][i])
{
printf("%c", argv[t][i]);
}
printf (" ");
}
return 0;
}
int main(int argc, char *argv)
{
register int i;
printf("%d files match specified name\n", argc-1);
printf("They are: ");
for(i=1; i
return 0;
}
int main(int argc, char *argv, char *env)
{
int t;
for(t=0; env[t]/ t++)
printf("%s\n", env[t]);
return 0;
}
#include
int main (int argc, char *argv, char *env)
{
int t;
for(t=0; env[t]; t++)
{
if(strstr(env[t], "PATH"))
printf("%s\n", env[t]);
}
return 0;
}
{…}
Если программу запускать через командную строку, то существует возможность передать какую-либо информацию этой программе. Для этого существуют аргументы командной строки argc
и argv
.
Параметр argc
имеет тип int
, и содержит количество параметров, передаваемых в функцию main
. Причем argc
всегда не меньше 1, даже когда функции main
не передается никакой информации, так как первым параметром считается имя приложения.
Параметр argv
представляет собой массив указателей на строки. Через командную строку можно передать только данные строкового типа.
Диск:\путь\имя.exe аргумент1 аргумент2 …
using namespace
std;
}
В программе используется функция преобразования строки в целое число StrToInt()
отсюда .
using namespace
std;
int
StrToInt(char
*s) {…}
int
main(int
argc, char
* argv) {
}Отладка программы с аргументами командной строки
На вкладке Свойства конфигурации ->Отладка
выбрать Аргументы команды
и задать их значения.Прототипы функций
Возврат значений (оператор return
)
Перегрузка функций
Передача аргументов функции по умолчанию
Области видимости и время жизни переменных.
Рекурсия
Функция main()
Передача указателей
Спецификаторы функций
Шаблоны функций
Указатели на функцию