Аргументи функції MAIN(). Декілька подробиць про функцію main Практичне значення передачі даних до програми

Одного разу зацікавився вмістом стека функції 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" .a_val); =%p-%p = %ld\n ", (void*)(*argv), (void*)auxv, (void*)(*argv) - (void*)auxv, (void*)(argv) , (void*)(&auxv), (void*)(argv) - (void*)(&auxv)); ))), 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 за корисне наведення.

Теги: Параметри командного рядка

Параметри командного рядка

С і - мова, що компілюється. Після складання програма являє собою файл, що виконується (ми не розглядаємо створення динамічних бібліотек, драйверів і т.д.). Наші програми дуже прості і не містять бібліотек часу виконання (Runtime libraries), тому можуть бути перенесені на комп'ютер з такою ж операційною системою (та подібною архітектурою) і там запущені.

Програма може запускати параметри під час запуску. Вони є аргументами функції main. Загальний вигляд функції main наступний

Void main(int argc, char **argv) ( ... )

Першим аргументом argc є кількість переданих функцій параметрів. Другий аргумент - масив рядків - власне самі параметри. Так як параметри функції можуть бути будь-якими, то вони передаються як рядки, і вже сама програма повинна їх розбирати і приводити до потрібного типу.

Першим аргументом (argv) є ім'я програми. При цьому ім'я виводиться залежно від того, звідки було запущено програму.

#include #include void main(int argc, char **argv) ( printf("%s", argv); )

Тепер навчимося трохи працювати з командним рядком. Це знадобиться для того, щоб передавати аргументи нашій програмі. Поєднання клавіш Win+R викликає вікно "Виконати". Наберіть у ньому cmd і ви відкриєте командний рядок. Також можна знайти cmd.exe пошуком у меню Пуск. У юнікс-подібних операційних системах можна викликати програму "термінал".

Ми не вивчатимемо багато команд. Тільки ті, які знадобляться у роботі.

Стандартна для всіх операційних систем команда cd здійснює перехід до потрібної папки. Існує два зарезервованих імені - . (точка) і.. (дві точки). Крапка – це ім'я поточної папки.

Нікуди не переходить

Звернення до батьківської папки

Перехід до батьківської папки

Для переходу за потрібним пишеться cd адресу. Наприклад, потрібно перейти на windows до папки C:\Windows\System32

Cd C:\Windows\System32

У лінуксі, якщо потрібно перейти в папку /var/mysql

Cd /var/mysql

Якщо шлях містить прогалини, він пишеться в подвійних лапках

Cd "D:\Docuents and Settings\Prolog"

Термінал має такі корисні особливості: якщо натиснути стрілку вгору, з'явиться попередня виконана команда. Якщо натиснути tab, то термінал спробує доповнити рядок до відомої команди, або доповнити шлях, перебираючи всі папки і файли в поточній папці.
Наберіть cd C:
натискайте tab та дивіться, що відбувається.

Ще одна важлива команда dir на windows і ls на linux, виводить на консоль вміст поточної папки (тої папки, в якій ви зараз перебуваєте)

Ваша програма повернула своє ім'я. Перейдіть до папки, де розміщена ваша програма і перегляньте її вміст


Тепер, після того, як ми перейшли до нашої папки, можна виконати нашу програму. Для цього наберіть її ім'я.


Зауважте – ім'я змінилося. Оскільки програма викликається зі своєї папки, виводиться щодо ім'я. Тепер змінимо програму та зробимо так, щоб вона виводила всі аргументи. які їй передані.

#include #include void main(int argc, char **argv) ( int i; for (i = 0; i< argc; i++) { printf("%s\n", argv[i]); } }

Зберіть проект. Перед збиранням переконайтеся, що програму закрито. Тепер викличте програму, передавши їй різні аргументи. Для цього напишіть ім'я програми та через пробіл аргументи


Давайте тепер напишемо програму, яка отримує два аргументи числа та виводить їх суму

#include #include #include void main(int argc, char **argv) ( int a, b; if (argc != 3) ( printf("Error: found %d arguments. Needs exactly 2", argc-1); exit(1); ) a = atoi(argv); b = atoi(argv); printf("%d", a + b); )

Зберемо та викличемо


Таким чином працює більшість програм. Клацаючи на ярлик, ви викликаєте програму, на яку він посилається. Більшість програм також приймають різні аргументи. Наприклад, можна викликати браузер firefox з командного рядка та передати аргументи
firefox.exe "www.mozilla.org" "сайт" і він відразу ж відкриє у двох вкладках сайти за вказаними адресами.

Багато стандартних команд також мають параметри. У windows прийнято, що вони починаються з прямого слеша, в юнікс з мінусу або двох мінусів. Наприклад

Виводить лише папки, а в терміналі linux

Ls -l виводить усі файли та папки із зазначенням атрибутів

Для перегляду додаткових команд windows наберіть у командному рядку help або дивіться посібник (його легко знайти в інтернеті). Для лінукса команд та їх опцій набагато більше, а деякі з них є самостійними мовами програмування, тому варто вивчити хоча б мінімальний набір та їх опції.

Зверніть увагу, у цій версії main() жодних параметрів немає. Тим не менш, багато програм потребують деяких вхідних даних. Наприклад, припустимо, що ви пишете програму під назвою Picture, яка приймає зображення як вхідні дані, а потім робить з цього зображення мініатюру (зменшена версія зображення). Як функція Picture дізнається, яке зображення потрібно прийняти та обробити? Користувач повинен повідомити програму, який файл слід відкрити. Це можна зробити так:

// Програма: Picture #include #include int main() ( std::cout<< "Enter name of image-file to create a thumbnail for: "; std::string filename; std::cin >> filename; // Відкриваємо файл-зображення // Створюємо мініатюру // Виводимо мініатюру)

Проте тут є потенційна проблема. Щоразу, при запуску, програма буде очікувати введення користувача. Це не проблема, якщо ви вручну запускаєте програму з командного рядка один раз для одного зображення. Але це вже проблема, якщо ви хочете працювати з великою кількістю файлів або інша програма мала можливість запустити цю програму.

Розглянемо це докладніше.

Наприклад, ви хочете створити мініатюри для всіх файлів-зображень, які знаходяться у певному каталозі. Як це зробити? Ви можете запускати цю програму стільки разів, скільки є зображень у каталозі, ввівши кожне ім'я файлу вручну. Однак, якщо є сотні зображень, такий підхід буде, м'яко кажучи, не надто ефективний! Рішенням тут буде написати програму, яка б перебирала кожне ім'я файлу в каталозі, викликаючи кожен раз Picture для кожного файлу.

Тепер розглянемо випадок, коли у вас є веб-сайт, і ви хочете, щоб він створював мініатюру щоразу, коли користувач завантажує зображення на сайт. Ця програма не може приймати вхідні дані з Інтернету і слідує логічне питання: «Як тоді вводити ім'я файлу?». Вихід є: виклик веб-сервером Picture автоматично кожного разу після завантаження файлу.

В обох випадках нам потрібно, щоб зовнішня програма передавала ім'я файлу як вхідні дані до нашої програми при її запуску, замість того, щоб Picture сам чекав, поки користувач вручну введе ім'я файлу.

Аргументи командного рядка- Це необов'язкові строкові аргументи, що передаються операційною системою в програму під час її запуску. Програма може їх використовувати як вхідні дані, або ігнорувати. Подібно до того, як параметри однієї функції надають дані для параметрів іншої функції, так і аргументи командного рядка надають можливість людям або програмам надавати вхідні дані для програми.

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

Програми, що виконуються, можуть запускатися в командному рядку через виклик. Наприклад, для запуску виконуваного файлу MyProgram, який знаходиться в кореневому каталозі диска C в Windows, вам потрібно ввести:

C:\>MyProgram

Щоб передати аргументи командного рядка MyProgram, вам потрібно буде їх просто перерахувати після імені файлу:

C:\>MyProgram SomeContent.txt

Тепер, при запуску MyProgram, SomeContent.txt буде надано як аргумент командного рядка. Програма може мати кілька аргументів командного рядка, розділених пробілами:

C:\>MyProgram SomeContent.txt SomeOtherContent.txt

Це також працює і з іншими операційними системами, наприклад, з Linux (хоча структура каталогів відрізнятиметься від структури каталогів у Windows).

Якщо ви запускаєте свою програму із середовища IDE, то ваша IDE має надати спосіб введення аргументів командного рядка.

Для користувачів Visual Studio: Клацніть правою кнопкою миші на потрібний проект у браузері Рішень, а потім виберіть «Властивості»:

Потім «Властивості конфігурації > Налагодження». На правій панелі буде рядок «Аргументи команди». Ви зможете тут ввести аргументи командного рядка, і вони будуть автоматично передані вашій програмі під час її запуску:

Користувачам Code::Blocksпотрібно вибрати «Project > Set program's arguments»:

Використання аргументів командного рядка

Тепер, коли ви знаєте, як передавати аргументи командного рядка до програми, наступним кроком буде доступ до них із програми. Для цього використовується вже інша форма функції main(), яка приймає два аргументи (argc та argv) таким чином:

int main(int argc, char *argv)

Також ви можете побачити і такий варіант:

int main(int argc, char** argv)

int main (int argc, char * * argv)

Хоча обидва ці варіанти ідентичні за своєю суттю, але рекомендується використовувати перший, оскільки він інтуїтивно зрозуміліший.

argc (англ. « arg ument c ount» = «кількість аргументів») — це цілий параметр, що містить кількість аргументів, переданих у програму. argc завжди буде щонайменше 1, тому що першим аргументом завжди є ім'я самої програми. Кожен аргумент командного рядка, який надає користувач, змусить argc збільшитись на 1.

argv (англ. « arg ument v alues» = «значення аргументів») це місце, де зберігаються фактичні значення аргументів. Хоча оголошення argv виглядає трохи лякаюче, але це лише масив . Довжина цього масиву - argc.

Давайте напишемо коротку програму MyArguments, яка виводитиме значення всіх аргументів командного рядка:

// Програма: MyArguments #include int main(int argc, char *argv) ( std::cout<< "There are " << argc << " arguments:\n"; // Перебираем каждый аргумент и выводим его порядковый номер и значение for (int count=0; count < argc; ++count) std::cout << count << " " << argv << "\n"; return 0; }

// Програма: MyArguments

#include

int main (int argc, char * argv)

// Перебираємо кожен аргумент і виводимо його порядковий номер та значення

for (int count = 0; count< argc ; ++ count )

std :: cout<< count << " " << argv [ count ] << "\n" ;

return 0;

Тепер, при виклику MyArguments з аргументами командного рядка SomeContent.txt і 200, висновок буде таким:

There are 3 arguments:
0 C:\MyArguments
1 SomeContent.txt
2 200

Параметр 0 – це шлях та ім'я поточної програми. Параметри 1 і 2 є аргументами командного рядка, які ми передали.

Обробка числових аргументів

Аргументи командного рядка завжди передаються як рядки, навіть якщо надане значення є числовим. Щоб використовувати аргумент командного рядка у вигляді числа, потрібно буде конвертувати його з рядка в число. На жаль, у C++ це робиться трохи складніше, ніж воно має бути:

#include #include #include // для std::stringstream #include // для exit() int main(int argc, char *argv) ( if (argc<= 1) { // В некоторых операционных системах, argv может быть просто пустой строкой, без имени программы // Обрабатываем случай, когда argv может быть пустым или не пустым if (argv) std::cout << "Usage: " << argv << " " << "\n"; else std::cout << "Usage: " << "\n"; exit(1); } std::stringstream convert(argv); // создаём переменную stringstream с именем convert, инициализируя её значением argv int myint; if (!(convert >> myint)) // виконуємо конвертацію myint = 0; // якщо конвертація зазнає невдачі, то задаємо myint значення за умовчанням std::cout<< "Got integer: " << myint << "\n"; return 0; }

#include

#include

#include // для std::stringstream

#include // для exit()

int main (int argc, char * argv)

if (argc<= 1 )

// У деяких операційних системах, argv може бути просто порожнім рядком, без імені програми

// Обробляємо випадок, коли argv може бути порожнім чи не порожнім

if (argv [0])

std :: cout<< "Usage: " << argv [ 0 ] << " " << "\n" ;

else

std :: cout<< "Usage: " << "\n" ;

exit (1);

При автоматизованому створенні консольної програми у мові програмування С++, автоматично створюється головна функція дуже схожа на цю:

int main(int argc, char * argv)
{…}

Заголовок функції містить сигнатуру головної функції main() з аргументами argс та argv.
Якщо програму запускати через командний рядок, існує можливість передати будь-яку інформацію цій програмі. І тому існують аргументи командного рядка argc і argv .
Параметр argc має тип int і містить кількість параметрів, що передаються в функцію main . Причому argc завжди щонайменше 1, навіть коли функції main не передається жодної інформації, оскільки першим параметром вважається ім'я програми.
Параметр argv є масивом покажчиків на рядки. Через командний рядок можна передати лише дані рядкового типу.

При запуску програми через командний рядок Windows можна надсилати певну інформацію. При цьому командний рядок матиме вигляд:
Диск:\шлях\ім'я.exe аргумент1 аргумент2 …

Аргументи командного рядка поділяються однією або декількома пробілами.

Аргумент argv містить повне ім'я програми:

#include
using namespace std;

cout<< argv << endl;

Return 0;
}

Результат виконання

Приклад: обчислення твору двох цілих чисел
У програмі використовується функція перетворення рядка на ціле число StrToInt() звідси.

#include
using namespace std;
int StrToInt(char *s) (…)
int main(int argc, char * argv) (

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;
}

Запуск програми здійснюється як

Результат виконання

Налагодження програми з аргументами командного рядка

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


На вкладці Властивості конфігурації ->Налагодженнявибрати Аргументи командита задати їх значення.

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

У програми мовою Сі можна передавати деякі аргументи. Коли спочатку обчислень проводиться звернення до 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, якщо використовуються стандартно певні макроімена. В іншому випадку ця змінна не буде визначена.