Однажды заинтересовался, содержимым стека функции main процесса в linux. Провел некоторые изыскания и теперь представляю вам результат.

Варианты описания функции main:
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)

Argc - число параметров
argv - нуль-терминальный массив указателей на строки параметров командной строки
env - нуль-терминальный массив указателей на строки переменных окружения. Каждая строка в формате ИМЯ=ЗНАЧЕНИЕ
auxv - массив вспомогательных значение (доступно только для PowerPC )
apple - путь к исполняемому файлу (в MacOS и Darwin )
Вспомогательный вектор - массив с различной дополнительной информацией, такой как эффективный идентификатор пользователя, признак setuid бита, размер страницы памяти и т.п.

Размер сегмента стека можно глянуть в файле maps:
cat /proc/10918/maps

7ffffffa3000-7ffffffff000 rw-p 00000000 00:00 0

Перед тем, как загрузчик передаст управление в main, он инициализирует содержимое массивов параметров командной строки, переменных окружения, вспомогательный вектор.
После инициализации верхняя часть стека выглядит примерно так, для 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. Переменные локальных функций

" - описания полей в документах не нашел, но в дампе явно видны.

Для 32 битов не проверял, но скорее всего достаточно только разделить размеры на два.

1. Обращение к адресам, выше верхней точки, вызывает Segfault.
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 #include int main(int argc, char** argv, char** env){ Elf64_auxv_t *auxv; //x86_64 // Elf32_auxv_t *auxv; //i386 while(*env++ != NULL); //ищем начало вспомогательного вектора for (auxv = (Elf64_auxv_t *)env; auxv->a_type != AT_NULL; auxv++){ printf("addr: %p type: %lx is: 0x%lx\n", auxv, auxv->a_type, auxv->a_un.a_val); } printf("\n (void*)(*argv) - (void*)auxv= %p - %p = %ld\n (void*)(argv)-(void*)(&auxv)=%p-%p = %ld\n ", (void*)(*argv), (void*)auxv, (void*)(*argv) - (void*)auxv, (void*)(argv), (void*)(&auxv), (void*)(argv) - (void*)(&auxv)); printf("\n argc copy: %d\n",*((int *)(argv - 1))); return 0; }
Структуры Elf{32,64}_auxv_t описаны в /usr/include/elf.h. Функции заполнения структур в linux-kernel/fs/binfmt_elf.c

Второй способ получить содержимое вектора:
hexdump /proc/self/auxv

Самый удобочитаемое представление получается установкой переменной окружения LD_SHOW_AUXV.

LD_SHOW_AUXV=1 ls
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()
После инициализации контекста процесса управление передается не в 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 void **ret; void *leave; void foo(){ void (*boo)(void); //указатель на функцию printf("Stack rewrite!\n"); boo = (void (*)(void))leave; boo(); // fini() } int main(int argc, char *argv, char *envp) { unsigned long int mark = 0xbfbfbfbfbfbfbfbf; //метка, от которой будем работать ret = (void**)(&mark+2); // извлекаем адрес, функции, вызываемой после завершения (fini) leave = *ret; // запоминаем *ret = (void*)foo; // перетираем return 0; // вызов функции foo() }

Надеюсь, было интересно.
Удач.

Спасибо пользователю Xeor за полезную наводку.

При создании консольного приложения в языке программирования С++, автоматически создается строка очень похожая на эту:

Int main(int argc, char* argv) // параметры функции main()

Эта строка — заголовок главной функции main() , в скобочках объявлены параметры argс и argv. Так вот, если программу запускать через командную строку, то существует возможность передать какую-либо информацию этой программе, для этого и существуют параметры argc и argv . Параметр argc имеет тип данных int , и содержит количество параметров, передаваемых в функцию main . Причем argc всегда не меньше 1, даже когда мы не передаем никакой информации, так как первым параметром считается имя функции. Параметр argv это массив указателей на строки. Через командную строку можно передать только данные строкового типа. Указатели и строки — это две большие темы, под которые созданы отдельные разделы. Так вот именно через параметр argv и передается какая-либо информация. Разработаем программу, которую будем запускать через командную строку Windows, и передавать ей некоторую информацию.

// argc_argv.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include using namespace std; int main(int argc, char* argv) { if (argc > << argv<

// код Code::Blocks

// код Dev-C++

// argc_argv.cpp: определяет точку входа для консольного приложения. #include using namespace std; int main(int argc, char* argv) { if (argc > 1)// если передаем аргументы, то argc будет больше 1(в зависимости от кол-ва аргументов) { cout << argv<

После того как отладили программу, открываем командную строку Windows и перетаскиваем в окно командной строки экзэшник нашей программы, в командной строке отобразится полный путь к программе(но можно прописать путь к программе в ручную), после этого можно нажимать ENTER и программа запустится (см. Рисунок 1).

Рисунок 1 — Параметры функции main

Так как мы просто запустили программу и не передавали ей никаких аргументов, появилось сообщение Not arguments . На рисунке 2 изображён запуск этой же программы через командную строку, но уже с передачей ей аргумента Open .

Рисунок 2 — Параметры функции main

Аргументом является слово Open , как видно из рисунка, это слово появилось на экране. Передавать можно несколько параметров сразу, отделяя их между собой запятой. Если необходимо передать параметр состоящий из нескольких слов, то их необходимо взять в двойные кавычки, и тогда эти слова будут считаться как один параметр. Например, на рисунке изображен запуск программы, с передачей ей аргумента, состоящего из двух слов — It work .

Рисунок 3 — Параметры функции main

А если убрать кавычки. То увидим только слово It . Если не планируется передавать какую-либо информацию при запуске программы, то можно удалить аргументы в функции main() , также можно менять имена данных аргументов. Иногда встречается модификации параметров argc и argv , но это все зависит от типа создаваемого приложения или от среды разработки.

Необязательные и именованные аргументы

Необязательные аргументы

В версии C# 4.0 внедрено новое средство, повышающее удобство указания аргументов при вызове метода. Это средство называется необязательными аргументами и позволяет определить используемое по умолчанию значение для параметра метода. Данное значение будет использоваться по умолчанию в том случае, если для параметра не указан соответствующий аргумент при вызове метода. Следовательно, указывать аргумент для такого параметра не обязательно. Необязательные аргументы позволяют упростить вызов методов, где к некоторым параметрам применяются аргументы, выбираемые по умолчанию. Их можно также использовать в качестве "сокращенной" формы перегрузки методов .

Главным стимулом для добавления необязательных аргументов послужила необходимость в упрощении взаимодействия с объектами СОМ. В нескольких объектных моделях Microsoft (например, Microsoft Office) функциональность предоставляется через объекты СОМ, многие из которых были написаны давно и рассчитаны на использование необязательных параметров.

Пример использования необязательных аргументов показан ниже:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { // Аргументы b и с указывать при вызове необязательно static int mySum(int a, int b = 5, int c = 10) { return a + b + c; } static void Main() { int sum1 = mySum(3); int sum2 = mySum(3,12); Console.WriteLine("Sum1 = "+sum1); Console.WriteLine("Sum2 = "+sum2); Console.ReadLine(); } } }

Следует иметь в виду, что все необязательные аргументы должны непременно указываться справа от обязательных. Помимо методов, необязательные аргументы можно применять в конструкторах, индексаторах и делегатах.

Преимущество необязательных аргументов заключается, в частности, в том, что они упрощают программирующему обращение со сложными вызовами методов и конструкторов. Ведь нередко в методе приходится задавать больше параметров, чем обычно требуется. И в подобных случаях некоторые из этих параметров могут быть сделаны необязательными благодаря аккуратному применению необязательных аргументов. Это означает, что передавать нужно лишь те аргументы, которые важны в данном конкретном случае, а не все аргументы, которые в противном случае должны быть обязательными. Такой подход позволяет рационализировать метод и упростить программирующему обращение с ним.

Именованные аргументы

Еще одной функциональной возможностью, которая добавилась в C# с выходом версии.NET 4.0, является поддержка так называемых именованных аргументов (named arguments) . Как известно, при передаче аргументов методу порядок их следования, как правило, должен совпадать с тем порядком, в котором параметры определены в самом методе. Иными словами, значение аргумента присваивается параметру по его позиции в списке аргументов.

Данное ограничение призваны преодолеть именованные аргументы. Именованный аргумент позволяет указать имя того параметра, которому присваивается его значение. И в этом случае порядок следования аргументов уже не имеет никакого значения. Таким образом, именованные аргументы в какой-то степени похожи на упоминавшиеся ранее инициализаторы объектов, хотя и отличаются от них своим синтаксисом. Для указания аргумента по имени служит следующая форма синтаксиса:

имя_параметра: значение

Здесь имя_параметра обозначает имя того параметра, которому передается значение. Разумеется, имя_параметра должно обозначать имя действительного параметра для вызываемого метода.

В программы на языке Си можно передавать некоторые аргументы. Когда вначале вычислений производится обращение к main(), ей передаются три параметра. Первый из них определяет число командных аргументов при обращении к программе. Второй представляет собой массив указателей на символьные строки, содержащие эти аргументы (в одной строке - один аргумент). Третий тоже является массивом указателей на символьные строки, он используется для доступа к параметрам операционной системы (к переменным окружения).

Любая такая строка представляется в виде:

переменная = значение\0

Последнюю строку можно найти по двум заключительным нулям.

Назовем аргументы функции main() соответственно: argc, argv и env (возможны и любые другие имена). Тогда допустимы следующие описания:

main(int argc, char *argv)

main(int argc, char *argv, char *env)

Предположим, что на диске A: есть некоторая программа prog.exe. Обратимся к ней следующим образом:

A:\>prog.exe file1 file2 file3

Тогда argv - это указатель на строку A:\prog.exe, argv - на строку file1 и т.д. На первый фактический аргумент указывает argv, а на последний - argv. Если argc=1, то после имени программы в командной строке параметров нет. В нашем примере argc=4.

Рекурсия

Рекурсией называется такой способ вызова, при котором функция обращается к самой себе.

Важным моментом при составлении рекурсивной программы является организация выхода. Здесь легко допустить ошибку, заключающуюся в том, что функция будет последовательно вызывать саму себя бесконечно долго. Поэтому рекурсивный процесс должен шаг за шагом так упрощать задачу, чтобы в конце концов для нее появилось не рекурсивное решение. Использование рекурсии не всегда желательно, так как это может привести к переполнению стека.

Библиотечные функции

В системах программирования подпрограммы для решения часто встречающихся задач объединяются в библиотеки. К числу таких задач относятся: вычисление математических функций, ввод/вывод данных, обработка строк, взаимодействие со средствами операционной системы и др. Использование библиотечных подпрограмм избавляет пользователя от необходимости разработки соответствующих средств и предоставляет ему дополнительный сервис. Включенные в библиотеки функции поставляются вместе с системой программирования. Их объявления даны в файлах *.h (это так называемые включаемые или заголовочные файлы). Поэтому, как уже упоминалось выше, в начале программы с библиотечными функциями должны быть строки вида:

#include <включаемый_файл_типа_h>

Например:

#include

Существуют также средства для расширения и создания новых библиотек с программами пользователя.

Для глобальных переменных отводится фиксированное место в памяти на все время работы программы. Локальные переменные хранятся в стеке. Между ними находится область памяти для динамического распределения.

Функции malloc() и free() используются для динамического распределения свободной памяти. Функция malloc() выделяет память, функция free() освобождает ее. Прототипы этих функций хранятся в заголовочном файле stdlib.h и имеют вид:

void *malloc(size_t size);

void *free(void *p);

Функция malloc() возвращает указатель типа void; для правильного использования значение функции надо преобразовать к указателю на соответствующий тип. При успешном выполнении функция возвращает указатель на первый байт свободной памяти размера size. Если достаточного количества памяти нет, возвращается значение 0. Чтобы определить количество байтов, необходимых для переменной, используют операцию sizeof().

Пример использования этих функций:

#include

#include

p = (int *) malloc(100 * sizeof(int)); /* Выделение памяти для 100

целых чисел */

printf("Недостаточно памяти\n");

for (i = 0; i < 100; ++i) *(p+i) = i; /* Использование памяти */

for (i = 0; i < 100; ++i) printf("%d", *(p++));

free(p); /* Освобождение памяти */

Перед использованием указателя, возвращаемого malloc(), необходимо убедиться, что памяти достаточно (указатель не нулевой).

Препроцессор

Препроцессор Си - это программа, которая обрабатывает входные данные для компилятора. Препроцессор просматривает исходную программу и выполняет следующие действия: подключает к ней заданные файлы, осуществляет подстановки, а также управляет условиями компиляции. Для препроцессора предназначены строки программы, начинающиеся с символа #. В одной строке разрешается записывать только одну команду (директиву препроцессора).

Директива

#define идентификатор подстановка

вызывает замену в последующем тексте программы названного идентификатора на текст подстановки (обратите внимание на отсутствие точки с запятой в конце этой команды). По существу, эта директива вводит макроопределение (макрос), где "идентификатор" - это имя макроопределения, а "подстановка" - последовательность символов, на которые препроцессор заменяет указанное имя, когда находит его в тексте программы. Имя макроопределения принято набирать прописными буквами.

Рассмотрим примеры:

Первая строка вызывает замену в программе идентификатора MAX на константу 25. Вторая позволяет использовать в тексте вместо открывающей фигурной скобки ({) слово BEGIN.

Отметим, что поскольку препроцессор не проверяет совместимость между символическими именами макроопределений и контекстом, в котором они используются, то рекомендуется такого рода идентификаторы определять не директивой #define, а с помощью ключевого слова const с явным указанием типа (это в большей степени относится к Си++):

const int MAX = 25;

(тип int можно не указывать, так как он устанавливается по умолчанию).

Если директива #define имеет вид:

#define идентификатор(идентификатор, ..., идентификатор) подстановка

причем между первым идентификатором и открывающей круглой скобкой нет пробела, то это определение макроподстановки с аргументами. Например, после появления строки вида:

#define READ(val) scanf("%d", &val)

оператор READ(y); воспринимается так же, как scanf("%d",&y);. Здесь val - аргумент и выполнена макроподстановка с аргументом.

При наличии длинных определений в подстановке, продолжающихся в следующей строке, в конце очередной строки с продолжением ставится символ \.

В макроопределение можно помещать объекты, разделенные знаками ##, например:

#define PR(x, у) x##y

После этого PR(а, 3) вызовет подстановку а3. Или, например, макроопределение

#define z(a, b, c, d) a(b##c##d)

приведет к замене z(sin, x, +, y) на sin(x+y).

Символ #, помещаемый перед макроаргументом, указывает на преобразование его в строку. Например, после директивы

#define PRIM(var) printf(#var"= %d", var)

следующий фрагмент текста программы

преобразуется так:

printf("year""= %d", year);

Опишем другие директивы препроцессора. Директива #include уже встречалась ранее. Ее можно использовать в двух формах:

#include "имя файла"

#include <имя файла>

Действие обеих команд сводится к включению в программу файлов с указанным именем. Первая из них загружает файл из текущего или заданного в качестве префикса каталога. Вторая команда осуществляет поиск файла в стандартных местах, определенных в системе программирования. Если файл, имя которого записано в двойных кавычках, не найден в указанном каталоге, то поиск будет продолжен в подкаталогах, заданных для команды #include <...>. Директивы #include могут вкладываться одна в другую.

Следующая группа директив позволяет избирательно компилировать части программы. Этот процесс называется условной компиляцией. В эту группу входят директивы #if, #else, #elif, #endif, #ifdef, #ifndef. Основная форма записи директивы #if имеет вид:

#if константное_выражение последовательность_операторов

Здесь проверяется значение константного выражения. Если оно истинно, то выполняется заданная последовательность операторов, а если ложно, то эта последовательность операторов пропускается.

Действие директивы #else подобно действию команды else в языке Си, например:

#if константное_выражение

последовательность_операторов_2

Здесь если константное выражение истинно, то выполняется последовательность_операторов_1, а если ложно - последовательность_операторов_2.

Директива #elif означает действие типа "else if". Основная форма ее использования имеет вид:

#if константное_выражение

последовательность_операторов

#elif константное_выражение_1

последовательность_операторов_1

#elif константное_выражение_n

последовательность_операторов_n

Эта форма подобна конструкции языка Си вида: if...else if...else if...

Директива

#ifdef идентификатор

устанавливает определен ли в данный момент указанный идентификатор, т.е. входил ли он в директивы вида #define. Строка вида

#ifndef идентификатор

проверяет является ли неопределенным в данный момент указанный идентификатор. За любой из этих директив может следовать произвольное число строк текста, возможно, содержащих инструкцию #else (#elif использовать нельзя) и заканчивающихся строкой #endif. Если проверяемое условие истинно, то игнорируются все строки между #else и #endif, а если ложно, то строки между проверкой и #else (если слова #else нет, то #endif). Директивы #if и #ifndef могут "вкладываться" одна в другую.

Директива вида

#undef идентификатор

приводит к тому, что указанный идентификатор начинает считаться неопределенным, т.е. не подлежащим замене.

Рассмотрим примеры. Три следующие директивы:

проверяют определен ли идентификатор WRITE (т.е. была ли команда вида #define WRITE...), и если это так, то имя WRITE начинает считаться неопределенным, т.е. не подлежащим замене.

Директивы

#define WRITE fprintf

проверяют является ли идентификатор WRITE неопределенным, и если это так, то определятся идентификатор WRITE вместо имени fprintf.

Директива #error записывается в следующей форме:

#error сообщение_об_ошибке

Если она встречается в тексте программы, то компиляция прекращается и на экран дисплея выводится сообщение об ошибке. Эта команда в основном применяется на этапе отладки. Заметим, что сообщение об ошибке не надо заключать в двойные кавычки.

Директива #line предназначена для изменения значений переменных _LINE_ и _FILE_, определенных в системе программирования Си. Переменная _LINE_ содержит номер строки программы, выполняемой в текущий момент времени. Идентификатор _FILE_ является указателем на строку с именем компилируемой программы. Директива #line записывается следующим образом:

#line номер "имя_файла"

Здесь номер - это любое положительное целое число, которое будет назначено переменной _LINE_, имя_файла - это необязательный параметр, который переопределяет значение _FILE_.

Директива #pragma позволяет передать компилятору некоторые указания. Например, строка

говорит о том, что в программе на языке Си имеются строки на языке ассемблера. Например:

Рассмотрим некоторые глобальные идентификаторы или макроимена (имена макроопределений). Определены пять таких имен: _LINE_, _FILE_, _DATE_, _TIME_, _STDC_. Два из них (_LINE_ и _FILE_) уже описывались выше. Идентификатор _DATE_ определяет строку, в которой сохраняется дата трансляции исходного файла в объектный код. Идентификатор _TIME_ задает строку, сохраняющую время трансляции исходного файла в объектный код. Макрос _STDC_ имеет значение 1, если используются стандартно - определенные макроимена. В противном случае эта переменная не будет определена.


Иногда при запуске программы бывает полезно передать ей какую-либо информацию. Обычно такая информация передается функции main() с помощью аргументов командной строки. Аргумент командной строки - это информация, которая вводится в командной строке операционной системы вслед за именем программы. Например, чтобы запустить компиляцию программы, необходимо в командной строке после подсказки набрать примерно следующее:

Cc имя_программы

имя_программы представляет собой аргумент командной строки, он указывает имя той программы, которую вы собираетесь компилировать.

Чтобы принять аргументы командной строки, используются два специальных встроенных аргумента: argc и argv . Параметр argc содержит количество аргументов в командной строке и является целым числом, причем он всегда не меньше 1, потому что первым аргументом считается имя программы. А параметр argv является указателем на массив указателей на строки. В этом массиве каждый элемент указывает на какой-либо аргумент командной строки. Все аргументы командной строки являются строковыми, поэтому преобразование каких бы то ни было чисел в нужный двоичный формат должно быть предусмотрено в программе при ее разработке.

Вот простой пример использования аргумента командной строки. На экран выводятся слово Привет и ваше имя, которое надо указать в виде аргумента командной строки.

#include #include int main(int argc, char *argv) { if(argc!=2) { printf("Вы забыли ввести свое имя.\n"); exit(1); } printf("Привет %s", argv); return 0; }

Если вы назвали эту программу name (имя) и ваше имя Том, то для запуска программы следует в командную строку ввести name Том. В результате выполнения программы на экране появится сообщение Привет, Том.

Во многих средах все аргументы командной строки необходимо отделять друг от друга пробелом или табуляцией. Запятые, точки с запятой и тому подобные символы разделителями не считаются. Например,

Run Spot, run

состоит из трех символьных строк, в то время как

Эрик, Рик, Фред

представляет собой одну символьную строку - запятые, как правило, разделителями не считаются.

Если в строке имеются пробелы, то, чтобы из нее не получилось несколько аргументов, в некоторых средах эту строку можно заключать в двойные кавычки. В результате вся строка будет считаться одним аргументом. Чтобы подробнее узнать, как в вашей операционной системе задаются параметры командной строки, изучите документацию этой системы.

Очень важно правильно объявлять argv . Вот как это делают чаще всего:

Char *argv;

Пустые квадратные скобки указывают на то, что у массива неопределенная длина. Теперь получить доступ к отдельным аргументам можно с помощью индексации массива argv . Например, argv указывает на первую символьную строку, которой всегда является имя программы; argv указывает на первый аргумент и так далее.

Другим небольшим примером использования аргументов командной строки является приведенная далее программа countdown (счет в обратном порядке). Эта программа считает в обратном порядке, начиная с какого-либо значения (указанного в командной строке), и подает звуковой сигнал, когда доходит до 0. Обратите внимание, что первый аргумент, содержащий начальное значение, преобразуется в целое значение с помощью стандартной функции atoi() . Если вторым аргументом командной строки (а если считать аргументом имя программы, то третьим) является строка «display» (вывод на экран), то результат отсчета (в обратном порядке) будет выводиться на экран.

/* Программа счета в обратном порядке. */ #include #include #include #include int main(int argc, char *argv) { int disp, count; if(argc<2) { printf("В командной строке необходимо ввести число, с которого\n"); printf("начинается отсчет. Попробуйте снова.\n"); exit(1); } if(argc==3 && !strcmp(argv, "display")) disp = 1; else disp = 0; for(count=atoi(argv); count; --count) if(disp) printf("%d\n", count); putchar("\a"); /* здесь подается звуковой сигнал */ printf("Счет закончен"); return 0; }

Обратите внимание, если аргументы командной строки не будут указаны, то будет выведено сообщение об ошибке. В программах с аргументами командной строки часто делается следующее: в случае, когда пользователь запускает эти программы без ввода нужной информации, выводятся инструкции о том, как правильно указывать аргументы.

Чтобы получить доступ к отдельному символу одного из аргументов командной строки, введите в argv второй индекс. Например, следующая программа посимвольно выводит все аргументы, с которыми ее вызвали:

#include int main(int argc, char *argv) { int t, i; for(t=0; t

Помните, первый индекс argv обеспечивает доступ к строке, а второй индекс - доступ к ее отдельным символам.

Обычно argc и argv используют для того, чтобы передать программе начальные команды, которые понадобятся ей при запуске. Например, аргументы командной строки часто указывают такие данные, как имя файла, параметр или альтернативное поведение. Использование аргументов командной строки придает вашей программе «профессиональный внешний вид» и облегчает ее использование в пакетных файлах.

Имена argc и argv являются традиционными, но не обязательными. Эти два параметра в функции main() вы можете назвать как угодно. Кроме того, в некоторых компиляторах для main() могут поддерживаться-дополнительные аргументы, поэтому обязательно изучите документацию к вашему компилятору.

Когда для программы не требуются параметры командной строки, то чаще всего явно декларируют функцию main() как не имеющую параметров. В таком случае в списке параметров этой функции используют ключевое слово void .