Аргументы функции MAIN(). Аргументы функции main() Области видимости и время жизни переменных

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

Функция main.

Каждая программа на С и C++ должна иметь функцию main; причем ваше дело, где вы ее поместите. Некоторые программисты помещают ее в начале файла, некоторые в конце. Однако независимо от ее положения необходимо помнить следующее: Аргументы функции "main". Запускающая процедура Borland C++ посылает функции main три параметра (аргумента): argc, argv и env. - argc, целое, - это число аргументов командной строки, посылаемое функции main, - argv это массив указателей на строки (char * ). Под версией DOS 3.x и более поздними argv определяется как полный маршрут запускаемой программы. При работе под более ранними версиями DOS argv указывает на нулевую строку (""). argv указывает на первую после имени программы строку командной строки. argv указывает на вторую после имени программы строку командной строки. argv указывает на последний аргумент, посылаемый функции main. argv содержит NULL. - env также является массивом указателей на строки. Каждый элемент env содержит строку вида ENVVAR=значение. ENVVAR - это имя переменной среды, типа PATH или 87. <значение> это значение данной переменной окружения, например C:\DOS;C:\TOOLS (для PATH) или YES (для 87). Заметим, однако, что если вы описываете некоторые из этих аргументов, то вы должны описывать их в таком порядке: argc, argv, env. Например, допустимы следующие объявления аргументов: main() main(int argc) /* допустимо но не очень хорошо */ main(int argc, char *argv) main(int argc, char *argv, char *env) Объявление main(int argc) не очень удобно тем, что зная количество параметров, вы не имеете доступа к ним самим. Аргумент env всегда доступен через глобальную переменную environ. Смотрите описание переменной environ (в Главе 3) и функции putenv и getenv (в Главе 2). Параметры argc и argv также доступны через переменные_argc и _argv. Пример программы, использующей argc, argv и env. Это пример программы ARGS.EXE, которая демонстрирует простейший путь использования аргументов, посылаемых функции main. /* программа ARGS.C */ #include #include void main(int argc, char *argv, char *env) { int i; printf("Значение argc равно %d \n\n",argc); printf("В командной строке содержится %d параметров \n\n",argc); for (i=0; i<=argc; i++) printf(" argv[%d]: %s\n",i,argv[i]); printf("Среда содержит следующие строки:\n"); for (i=0; env[i] != NULL; i++) printf(" env[%d]: %s\n",i,env[i]); return 0; } Предположим, что вы запускаете программу ARGS.EXE со следующей командной строкой: C:> args first_arg "arg with blanks" 3 4 "last but one" stop! Заметим, что вы можете послать аргумент с пробелами, заключив его в двойные кавычки, как показано на примере "argument with blanks" и "last but one" в примере вызова программы. В результате работы программы вы получите примерно следующее: Значение argc равно 7 В командной строке содержится 7 параметров argv: c:\turboc\testargs.exe argv: first_arg argv: arg with blank argv: 3 argv: 4 argv: last but one argv: stop! Среда содержит следующие строки: env: COMSPEC=C:\COMMAND.COM env: PROMPT=$p $g env: PATH=C:\SPRINT;C:\DOS;C:\BC Максимальная общая длина командной строки, посылаемая функции main (включая пробелы и имя самой программы), не может превышать 128 символов; это ограничения DOS. Символы маскирования в командной строке Аргументы командной строки могут содержать символы маскирования. При этом они могут расширяться для всех имен файлов, которые совпадают с аргументом так, как это делается, например, с командой DOS copy. Для использования символов маскирования необходимо при связывании вашей программы редактором связей подсоединить к ней объектный файл WILDARGS.OBJ, который поставляется с Borland C++. Если файл WILDARGS.OBJ подсоединен к вашей программе, то вы можете в командной строке использовать аргументы типа "*.*". При этом имена всех файлов, подходящих к данной маске, заносятся в массив argv. Максимальный размер массива argv зависит только от объема динамической области памяти. Если под данную маску не нашлось подходящих файлов, то аргумент передается в том виде, в каком он был набран в командной строке. (Т.е. функции main передается строка, содержащая символы маскирования). Аргументы, заключенные в двойные кавычки ("..."), не расширяются. Пример. Следующие команды компилируют файл ARGS.C и связывают его с модулем WILDARGS.OBJ, а затем запускают получившуюся программу ARGS.EXE: bcc args wildarg.obj args C:\BORLANDC\INCLUDE\*.H "*.C" При запуске ARGS.EXE первый аргумент расширяется до имен всех файлов с расширением H в директории Borland C++ INCLUDE. Отметим, что все строки включают полный маршрут (к примеру C:\TC\INCLUDE\ALLOC.H). Аргумент *.C не расширяется, т.к. он заключен в кавычки. Если вы работаете в Интегрированном Окружении (BC.EXE), то вам просто нужно указать в меню проекта имя файла проекта, который должен содержать следующие строки: ARGS WILDARGS.OBJ Затем с помощью команд "Run/Arguments" следует установить параметры командной строки. Замечание. Если вы хотите, чтобы обработка символов маскирования происходила всегда, т.е. чтобы WILDARGS.OBJ автоматически подсоединялся редактором связей, вы должны модифицировать вашу стандартную библиотеку C?.LIB, добавив в нее файл WILDARGS.OBJ. Для этого удалите из библиотеки SETARGV и добавьте WILDARGS. Это можно сделать с помощью следующих команд (мы подразумеваем, что стандартные библиотеки и WILDARGS.OBJ содержатся в текущей директории): TLIB описана в главе 7 "Утилиты" документа "User"s Guide". tlib cs -setargv +wildargs tlib cc -setargv +wildargs tlib cm -setargv +wildargs tlib cl -setargv +wildargs tlib ch -setargv +wildargs Компиляция с использованием ключа -p (Соглашение по вызову языка Паскаль). Если вы компилируете вашу программу, используя соглашение по вызову языка Паскаль (детально описано в главе 9 "Interfacing with assembly languige", "Programmer"s Guide"), вы должны помнить, что функция main должна быть явно объявлена как функция С. Это можно сделать с помощью ключевого слова cdecl примерно так: cdecl main(int argc, char *argv, char *env) Значение, возвращаемое функцией main. Функция main возвращает значение, которое является кодом завершения программы: это целое. Однако, если ваша программа для завершения использует функцию exit (или _exit), то возвращаемым значением будет аргумент этой функции. Например, если ваша программа содержит вызов: exit(1) то код завершения будет равен 1. Если для запуска программы вы используете интегрированное окружение Borland C++ (BC.EXE), то посмотреть возвращаемое значение функции main вы можете, выбрав "File | Get Info".

Однажды заинтересовался, содержимым стека функции 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 за полезную наводку.

Теги: Функции в си, прототип, описание, определение, вызов. Формальные параметры и фактические параметры. Аргументы функции, передача по значению, передача по указателю. Возврат значения.

Введение

Ч ем дальше мы изучаем си, тем больше становятся программы. Мы собираем все действия в одну функцию main и по несколько раз копируем одни и те же действия, создаём десятки переменных с уникальными именами. Наши программы распухают и становятся всё менее и менее понятными, ветвления становятся всё длиннее и ветвистее.

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

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

Мы уже знакомы с многими функциями и знаем, как их вызывать – это функции библиотек stdio, stdlib, string, conio и пр. Более того, main – это тоже функция. Она отличается от остальных только тем, что является точкой входа при запуске приложения.
Функция в си определяется в глобальном контексте. Синтаксис функции: (, ...) { }

Самый простой пример – функция, которая принимает число типа float и возвращает квадрат этого числа

#include #include float sqr(float x) { float tmp = x*x; return tmp; } void main() { printf("%.3f", sqr(9.3f)); getch(); }

Внутри функции sqr мы создали локальную переменную, которой присвоили значение аргумента. В качестве аргумента функции передали число 9,3. Служебное слово return возвращает значение переменной tmp. Можно переписать функцию следующим образом:

Float sqr(float x) { return x*x; }

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

Void printSqr(float x) { printf("%d", x*x); return; }

в данном случа return означает выход из функции. Если функция ничего не возвращает, то return можно не писать. Тогда функция доработает до конца и произойдёт возврат управления вызывающей функции.

Void printSqr(float x) { printf("%d", x*x); }

Если функция не принимает аргументов, то скобки оставляют пустыми. Можно также написать слово void:

Void printHelloWorld() { printf("Hello World"); }

эквивалентно

Void printHelloWorld(void) { printf("Hello World"); }

Формальные и фактические параметры

П ри объявлении функции указываются формальные параметры, которые потом используются внутри самой функции. При вызове функции мы используем фактические параметры. Фактическими параметрами могут быть переменные любого подходящего типа или константы.

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

#include #include //Формальные параметры имеют имена a и b //по ним мы обращаемся к переданным аргументам внутри функции int sum(int a, int b) { return a+b; } float square(float x) { return x*x; } void main() { //Фактические параметры могут иметь любое имя, в том числе и не иметь имени int one = 1; float two = 2.0; //Передаём переменные, вторая переменная приводится к нужному типу printf("%d\n", sum(one, two)); //Передаём числовые константы printf("%d\n", sum(10, 20)); //Передаём числовые константы неверного типа, они автоматически приводится к нужному printf("%d\n", sum(10, 20.f)); //Переменная целого типа приводится к типу с плавающей точкой printf("%.3f\n", square(one)); //В качестве аргумента может выступать и вызов функции, которая возвращает нужное значение printf("%.3f\n", square(sum(2 + 4, 3))); getch(); }

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

#include #include void main() { char c; do { //Сохраняем возвращённое значение в переменную c = getch(); printf("%c", c); } while(c != "q"); //Возвращённое значение не сохраняется getch(); }

Передача аргументов

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

#include #include void change(int a) { a = 100; printf("%d\n", a); } void main() { int d = 200; printf("%d\n", d); change(d); printf("%d", d); getch(); }

Программы выведет
200
100
200
Понятно почему. Внутри функции мы работаем с переменной x, которая является копией переменной d. Мы изменяем локальную копию, но сама переменная d при этом не меняется. После выхода из функции локальная переменная будет уничтожена. Переменная d при этом никак не изменится.
Каким образом тогда можно изменить переменную? Для этого нужно передать адрес этой переменной. Перепишем функцию, чтобы она принимала указатель типа int

#include #include void change(int *a) { *a = 100; printf("%d\n", *a); } void main() { int d = 200; printf("%d\n", d); change(&d); printf("%d", d); getch(); }

Вот теперь программа выводит
200
100
100
Здесь также была создана локальная переменная, но так как передан был адрес, то мы изменили значение переменной d, используя её адрес в оперативной памяти.

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

#include #include #include void init(int *a, unsigned size) { a = (int*) malloc(size * sizeof(int)); } void main() { int *a = NULL; init(a, 100); if (a == NULL) { printf("ERROR"); } else { printf("OKAY..."); free(a); } getch(); }

Но эта функция выведет ERROR. Мы передали адрес переменной. Внутри функции init была создана локальная переменная a, которая хранит адрес массива. После выхода из функции эта локальная переменная была уничтожена. Кроме того, что мы не смогли добиться нужного результата, у нас обнаружилась утечка памяти: была выделена память на куче, но уже не существует переменной, которая бы хранила адрес этого участка.

Для изменения объекта необходимо передавать указатель на него, в данном случае – указатель на указатель.

#include #include #include void init(int **a, unsigned size) { *a = (int*) malloc(size * sizeof(int)); } void main() { int *a = NULL; init(&a, 100); if (a == NULL) { printf("ERROR"); } else { printf("OKAY..."); free(a); } getch(); }

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

#include #include #include #include char* initByString(const char *str) { char *p = (char*) malloc(strlen(str) + 1); strcpy(p, str); return p; } void main() { char *test = initByString("Hello World!"); printf("%s", test); free(test); getch(); }

В этом примере утечки памяти не происходит. Мы выделили память с помощью функции malloc, скопировали туда строку, а после этого вернули указатель. Локальные переменные были удалены, но переменная test хранит адрес участка памяти на куче, поэтому можно его удалить с помощью функции free.

Объявление функции и определение функции. Создание собственной библиотеки

В си можно объявить функцию до её определения. Объявление функции, её прототип, состоит из возвращаемого значения, имени функции и типа аргументов. Имена аргументов можно не писать. Например

#include #include //Прототипы функций. Имена аргументов можно не писать int odd(int); int even(int); void main() { printf("if %d odd? %d\n", 11, odd(11)); printf("if %d odd? %d\n", 10, odd(10)); getch(); } //Определение функций int even(int a) { if (a) { odd(--a); } else { return 1; } } int odd(int a) { if (a) { even(--a); } else { return 0; } }

Это смешанная рекурсия – функция odd возвращает 1, если число нечётное и 0, если чётное.

Обычно объявление функции помещают отдельно, в.h файл, а определение функций в.c файл. Таким образом, заголовочный файл представляет собой интерфейс библиотеки и показывает, как с ней работать, не вдаваясь в содержимое кода.

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

#ifndef _FILE1_H_ #define _FILE1_H_ int odd(int); int even(int); #endif

Содержимое файла исходного кода File1.c

#include "File1.h" int even(int a) { if (a) { odd(--a); } else { return 1; } } int odd(int a) { if (a) { even(--a); } else { return 0; } }

Наша функция main

#include #include #include "File1.h" void main() { printf("if %d odd? %d\n", 11, odd(11)); printf("if %d odd? %d\n", 10, odd(10)); getch(); }

Рассмотрим особенности каждого файла. Наш файл, который содержит функцию main, подключает необходимые ему библиотеки а также заголовочный файл File1.h. Теперь компилятору известны прототипы функций, то есть он знает возвращаемый тип, количество и тип аргументов и имена функций.

Заголовочный файл, как и оговаривалось ранее, содержит прототип функций. Также здесь могут быть подключены используемые библиотеки. Макрозащита #define _FILE1_H_ и т.д. используется для предотвращения повторного копирования кода библиотеки при компиляции. Эти строчки можно заменить одной

#pragma once int odd(int); int even(int);

Файл File1.c исходного кода подключает свой заголовочный файл. Всё как обычно логично и просто. В заголовочные файлах принято кроме прототипов функций выносить константы, макроподстановки и определять новые типы данных. Кроме того, именно в заголовочных файлах можно обширно комментировать код и писать примеры его использования.

Передача массива в качестве аргумента

К ак уже говорилось ранее, имя массива подменяется на указатель, поэтому передача одномерного массива эквивалентна передаче указателя. Пример: функция получает массив и его размер и выводит на печать:

#include #include void printArray(int *arr, unsigned size) { unsigned i; for (i = 0; i < size; i++) { printf("%d ", arr[i]); } } void main() { int x = {1, 2, 3, 4, 5}; printArray(x, 10); getch(); }

В этом примере функция может иметь следующий вид

Void printArray(int arr, unsigned size) { unsigned i; for (i = 0; i < size; i++) { printf("%d ", arr[i]); } }

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

#include #include void printArray(int arr, unsigned size) { unsigned i, j; for (i = 0; i < size; i++) { for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void main() { int x = { { 1, 2, 3, 4, 5}, { 6, 7, 8, 9, 10}}; printArray(x, 2); getch(); }

Либо, можно писать

#include #include void printArray(int (*arr), unsigned size) { unsigned i, j; for (i = 0; i < size; i++) { for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void main() { int x = { { 1, 2, 3, 4, 5}, { 6, 7, 8, 9, 10}}; printArray(x, 2); getch(); }

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

#include #include #include #include #define SIZE 10 unsigned* getLengths(const char **words, unsigned size) { unsigned *lengths = NULL; unsigned i; lengths = (unsigned*) malloc(size * sizeof(unsigned)); for (i = 0; i < size; i++) { lengths[i] = strlen(words[i]); } return lengths; } void main() { char **words = NULL; char buffer; unsigned i; unsigned *len = NULL; words = (char**) malloc(SIZE * sizeof(char*)); for (i = 0; i < SIZE; i++) { printf("%d. ", i); scanf("%127s", buffer); words[i] = (char*) malloc(128); strcpy(words[i], buffer); } len = getLengths(words, SIZE); for (i = 0; i < SIZE; i++) { printf("%d ", len[i]); free(words[i]); } free(words); free(len); getch(); }

Можно вместо того, чтобы возвращать указатель на массив, передавать массив, который необходимо заполнить

#include #include #include #include #define SIZE 10 void getLengths(const char **words, unsigned size, unsigned *out) { unsigned i; for (i = 0; i < size; i++) { out[i] = strlen(words[i]); } } void main() { char **words = NULL; char buffer; unsigned i; unsigned *len = NULL; words = (char**) malloc(SIZE * sizeof(char*)); for (i = 0; i < SIZE; i++) { printf("%d. ", i); scanf("%127s", buffer); words[i] = (char*) malloc(128); strcpy(words[i], buffer); } len = (unsigned*) malloc(SIZE * sizeof(unsigned)); getLengths(words, SIZE, len); for (i = 0; i < SIZE; i++) { printf("%d ", len[i]); free(words[i]); } free(words); free(len); getch(); }

На этом первое знакомство с функциями заканчивается: тема очень большая и разбита на несколько статей.

Страница 53 из 85

1.5.3. Передача параметров функции main

Функция main, с которой начинается выполнение программы на языке программирования С, может быть определена с параметрами, которые передаются из внешнего окружения, например, из командной строки. Во внешнем окружении действуют свои правила представления данных, а точнее, все данные представляются в виде строк символов. Для передачи этих строк в функцию main используются два параметра, первый параметр служит для передачи числа передаваемых строк, второй для передачи самих строк. Общепринятые (но не обязательные) имена этих параметров argc и argv. Параметр argc имеет тип int, его значение формируется из анализа командной строки и равно количеству слов в командной строке, включая и имя вызываемой программы (под словом понимается любой текст не содержащий символа пробел). Параметр argv это массив указателей на строки, каждая из которых содержит одно слово из командной строки. Если слово должно содержать символ пробел, то при записи его в командную строку оно должно быть заключено в кавычки.

Функция main может иметь и третий параметр, который принято называть argp, и который служит для передачи в функцию main параметров операционной системы (среды) в которой выполняется программа на языке программирования С.

Заголовок функции main имеет вид:

Если, например, командная строка программы на языке программирования С имеет вид:

A:\>cprog working "C program" 1

то аргументы argc, argv, argp представляются в памяти как показано в схеме на рис.1.

Argc [ 4 ]
argv --> -->
-->
-->
-->
argp --> -->
-->
-->
-->
Рис.1. Схема размещения параметров командной строки

Операционная система поддерживает передачу значений для параметров argc, argv, argp, а на пользователе лежит ответственность за передачу и использование фактических аргументов функции main.

Следующий пример представляет программу печати фактических аргументов, передаваемых в функцию main из операционной системы и параметров операционной системы.

Пример:
int main (int argc, char *argv, char *argp)
{ int i=0;
printf ("\n Имя программы %s", argv);
for (i=1; i>=argc; i++)
printf ("\n аргумент %d равен %s", argv[i]);
printf ("\n Параметры операционной системы:");
while (*argp)
{ printf ("\n %s",*argp);
argp++;
}
return (0);
}

Доступ к параметрам операционной системы можно также получить при помощи библиотечной функции geteuv, ее прототип имеет следующий вид:

char *geteuv (const char *varname);

Аргумент этой функции задает имя параметра среды, указатель на значение которой выдаст функция geteuv. Если указанный параметр не определен в среде в данный момент, то возвращаемое значение NULL.

Используя указатель, полученный функцией geteuv, можно только прочитать значение параметра операционной системы, но нельзя его изменить. Для изменения значения параметра системы предназначена функция puteuv.

Компилятор языка программирования С строит С-программу таким образом, что вначале работы программы выполняется некоторая инициализация, включающая, кроме всего прочего, обработку аргументов, передаваемых функции main, и передачу ей значений параметров среды. Эти действия выполняются библиотечными функциями _setargv и _seteuv, которые всегда помещаются компилятором перед функцией main.

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

Setargv()
}
-seteuv()
{ return ; /* пустая функция */
}
int main()
{ /* главная функция без аргументов */
...
...
renurn (0);
}

В приведенной программе при вызове библиотечных функций _setargv и _seteuv будут использованы функции помещенные в программу пользователем и не выполняющие никаких действий. Это заметно снизит размер получаемого exe-файла.

Основная форма записи функции имеет следующий вид

тип_возврата имя_функции (список_параметров ) { тело_функции } Тип данных, возвращаемых функцией, задаётся с помощью элемента тип_возврата . Под элементом список_параметров подразумевается список разделяемых запятыми переменных которые могут принимать любые аргументы, передаваемой функцией.

В версии C89, если тип данных возвращаемый функцией, явно не задан, то подразумевается тип int . В языке C++ и версии C99, тип int по умолчанию, не поддерживается, хотя в большинстве компиляторов C++ такое предположение остаётся в силе.

Прототипы функций

В языке C++ все функции должны иметь прототипы, а в языке C прототипы формально необязательны, но весьма желательны. Общая форма определения прототипа имеет следующий вид.

тип_возврата имя_функции (список_параметров ); Например. float fn(float x); //или float fn(float);

В языке C для задания прототипа функции, не имеющей параметров, вместо списка параметров используется ключевое слово void . В языке C++ пустой список параметров в прототипе функция означает, что функция на имеет параметров. Слово void в этом случае необязательно.

Возврат значений (оператор return )

Возврат значений в функции осуществляется с помощью оператора 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; }

Функция main()

Выполнение 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 using namespace std; int main (int argc, char *argv) { if (argc

Передача указателей

Несмотря на то что в языках 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
  • virtual
  • explict

Спецификатор inline представляет собой обращённое к компилятору требование: вместо создания вызова функции раскрыть её код прямо в строке. Если компилятор не может вставить функцию в строку он имеет право проигнорировать это требование. Спецификатором inline могут определяться как функции-члены, так и не функции-члены.

В качестве виртуальной функции (с помощью спецификатора virual ) ф. определяется в базовом классе и переопределяется в производным классом. На примере виртуальных функций можно понять, как язык C++ поддерживает полиморфизм.

Спецификатор explicit применяется только к конструкторам, Конструктор, определённый как explicit , будет задействован только в том случае, когда инициализация в точности соответствует тому, что задано конструктором. Никаких преобразований выполняться не будет (т.е. спецификатор explicit создаёт "неконвертирующий конструктор").

Шаблоны функций

Общая форма определения шаблонной функции имеет следующий вид.

Tetemplate тип> тип_возврата имя_функции (список_параметров ) { //тело функции } Здесь тип означает метку - заполнитель для типа данных, с которыми эта функция фактически будет иметь дело. В операторе template можно определить несколько параметров-типов данных, используя форму списка элементов, разделённых запятыми.

Рассмотрим пример.

Template void swap (X &a, X &b) { X temp; temp = a; a = b; b = temp; } //вызов int a, b; float x, y; swap (a, b); swap (x, y);

Указатели на функцию

На функцию, как и на любой другой объект языка 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



error: Content is protected !!