### Стек и куча У любой функции есть область в памяти - кадр, где хранится информация о ней, в том числе адрес возврата по завершении и место для хранения всех автоматических переменных. Когда функция (`main`) вызывает другую функцию, то операции в кадре первой функции приостанавливаются, а для вызванной функции создается новый кадр и помещается в стек кадров. По окончании работы функции ее кадр выталкивается из стека, а все хранившиеся в этом кадре переменные пропадают. На размер стека наложены произвольно выбранные ограничения, поэтому стек гораздо меньше памяти общего назначения - в районе 2–3 мегабайтов. Этого хватит для хранения всех трагедий Шекспира, так что о размещении массива из 10 000 целых можете не беспокоиться. Но наборы данных бывают куда больше, а из-за ограничений на размер стека приходится подыскивать для них место где-то еще, а это значит - прибегать к помощи malloc. Функция malloc выделяет память не в стеке, а в области, которая называется кучей. Размер кучи может быть ограничен или не ограничен; при работе на типичном ПК вполне можно считать, что размер кучи примерно равен объему всей доступной памяти. ### В чем разница между `int an_array[]` и `int *а_pointer`? Встретив такое объявление: ``` int an_array[32]; ``` Программа выполняет следующие действия: * отводит в стеке область для размещения 32 целых; * объявляет переменную `an_array` как указатель; * связывает этот указатель с только что отведенной областью памяти. Эта область выделена в автоматической памяти, то есть вы не можете ни изменить ее размер, ни сохранить данные в ней после автоматического уничтожения по выходе из области видимости. Есть и еще одно ограничение - указатель `an_array` нельзя направить куда-то еще. Поскольку переменную an_array нельзя «развести » с выделенной для нее областью для хранения 32 целых. Несмотря на указанные ограничения, `an_array` - все же указатель на область в памяти, и к нему применимы обычные правила разыменования указателей. Встретив же такое объявление: ``` int * a_pointer; ``` программа выполняет только один и з вышеописанных шагов: * объявляет переменную `an _ array` как указатель. Этот указатель не привязан намертво к какому-то адресу в памяти, поэтому его можно перенаправить на любой другой адрес. ``` // динамически выделить блок памяти, направив на него указатель a_pointer: a_pointer = malloc(32*sizeof (int)) ; // направить указатель на массив, объявленный выше : a_pointer = an_array ; ``` ### Указатели без malloc Говоря компьютеру установи А в В, я могу иметь в виду одно из двух. * Скопировать значение В в А. Если я затем инкрементирую А с помощью операции А++, то В не изменится. * Cделать А псевдонимом В. В этом случае операция А++ приводит также к инкременту В. **Правило 1. Структуры копируются, для массивов создаются псевдонимы** ``` #include <assert.h> typedef struct { int a, b; double c, d; int *efg; } demo_s; int main() { demo_s d1 = {.b = 1, .c = 2, .d = 3, .efg = (int[]){4, 5, 6}}; demo_s d2 = d1; d1.b = 14; d1.c = 41; d1.efg[0] = 7; assert(d2.a == 0); assert(d2.b == 1); assert(d2.c == 2); assert(d2.d == 3); assert(d2.efg[0] == 7); } ``` Как всегда, вы должны понимать, является ли операция присваивания копированием данных или созданием псевдонима. И как же обстоит дело здесь? Мы изменили d1.b и d1.с, но d2 не изменилась, следовательно, это копирование. Но копия указателя по-прежнему указывает на исходные данные, поэтому изменение dl.efg[0] отражается и на d2.efg. Отсюда вывод: если нужно глубокое копирование, то есть копирование данных, на которые ведут указатели, то понадобится функция копирования структуры; если же прослеживать указатели не нужно, то функция копирования - излишество, достаточно простого знака равенства. Для массивов знак равенства копирует лишь псевдоним, но не сами данные. **Правило 2. Структуру можно возвращать из функции , массив - нельзя** Функция, которая завершается предложением return х , возвращает значение х вызывающей функции. Звучит просто, но это значение должно быть скопировано в вызывающую функцию, так как кадр вызванной функции очень скоро будет уничтожен. Как и раньше, в случае структуры, числа и даже указателя вызывающая функция получает копию возвращенного значения, а вот в случае массива - указатель на массив, а не копию хранящихся в нем данных. Тут-то и скрыта ловушка, потому что возвращенный указатель может указывать на область автоматической памяти, выделенной для данных массива, а она при выходе из функции уничтожается. Указатель на освобожденную область памяти хуже, чем бесполезен. ``` #include <stdio.h> typedef struct { double base, square, cube; } powers; /* * Так можно. При выходе создается копия автоматической локальной * переменной, после чего оригинал уничтожается. */ powers get_power(double in) { powers out = {.base = in, .square = in * in, .cube = in * in * in}; return out; } /* * А так нельзя. Здесь массив трактуется как указатель, поэтому при выходе * создается копия указателя. Но после того как автоматически выделенная *память будет освобождена, указатель указывает в никуда. Если ваш * компилятор достаточно смышленый, то он предупредит об этом. */ int *get_even(int count) { int out[count]; for (int i = 0; i < count; i++) { out[i] = 2 * i; } return out; // Плохо * } int main() { powers threes = get_power(3); int *evens= get_even(3); printf("threes %g\t%g\t%g\n", threes.base, threes.square, threes.cube); printf("threes %i\t%i\t%i\n", evens[0], evens[1], evens[2]); } ``` **Правило 3. Для копирования массива нужна функция `memmove` - допотопная п рактика , но работает** ``` #include <assert.h> #include <string.h> int main() { int abc[] = {0, 1, 2}; int *copy1, copy2[3]; copy1 = abc; memmove(copy2, abc, sizeof(int) * 3); abc[0] = 3; assert(copy1[0] == 3); assert(copy2[0] == 0); } ``` ### malloc и игрища с памятью Самый простой способ избежать ошибок, связанных с `malloc`, - не пользоваться `malloc` вовсе. Перечень оставшихся причин для использования `malloc`. 0. Для изменения размера существующего массива необходима функция realloc, но применять ее можно только к областям памяти, первоначально выделенным с помощью malloc. 0. Как объяснялось выше, возвращать массив из функции нельзя. 0. Иногда объекты должны существовать в течение длительного времени после выхода из создавшей их функции. 0. Автоматическая память выделяется в стеке, размер которого обычно ограничен несколькими мегабайтами (или того меньше). Поэтому большие участки памяти (порядка мегабайтов) следует выделять в куче, а не в стеке. Но опять же, у вас, наверное, есть функция, которая сохраняет данные в каком-то объекте, и называется она как-то вроде object_new, так что напрямую к malloc вы все равно не обращаетесь. 5. Иногда встречаются функции, которые возвращают указатель. **Правило 3. в объявлении звездочка обозначает указатель, вне объявления - значение указателя. ** ``` int *i = malloc(sizeof(int)); // правильно *i = 23; // правильно int *i = 23; // ошибка ``` ### Все, что нужно знать об арифметике указателей Элемент массива можно определить c помощью смещения от начала массива. Правила обозначения массивов и их элементов. * Объявляйте массив в виде явного указателя `douЬle *р` или в статической либо автоматической форме `double р[100]`. * Индекс первого элемента равен нулю, а не единице; на него можно сослаться особым образом: `р[0] == *р`. * Чтобы получить адрес n-го элемента (не его значение), пользуйтесь знаком амперсанда: `&р[n]` . Разумеется, для первого элемента имеем `&р [0] == р`. ### Typedef как педагогический инструмент Всякий раз конструируя сложный тип, который зачастую включает такие вещи как указатель на указатель на указатель, спросите себя, не станет ли задача яснее, если воспользоваться псевдонимом типа `typedef`. Например, такое популярное определение: ``` typedef char *string; ``` Псевдонимы типов очень хороши для работы с указателями на функции. Если имеется такой заголовок функции: ``` doule a_fn(int, int ); // обьявление ``` то для описания указателя на функцию подобного типа нужно только добавить звездочку (и скобки для учета приоритета операторов): ``` doule (*а_fn_type)(int, int); // тип: указатель на функцию ``` Затем поместим в начало typedef и тем самым определим новый псевдоним типа: ``` typedef double (*a_fn_type)(int, int); // typedef для указателя на функцию ``` Теперь этот тип можно использовать, как любой другой, например в объявлении функции, принимающей другую функцию в качестве аргумента: ``` double apply_a_fn(a_fn_type f, int fist_in, int second_in) { return f(first_in, second_in) ; } ``` Правило 4. Ни к чему явно возврашать эначение из main Стандарт С гласит: « ... если выполнение программы доходит до закрывающей скобки }, которая завершает функцию main, то возвращается значение 0. если не написать return 0; в последней строке main, то это будет подразумеваться по умолчанию. ### Меньше приведений Две причины для использования синтаксиса приведения типов переменных в С. 1. при делении целого числа на целое возвращается целый результат, поэтому оба следующих утверждения истинны: ``` 4/2 == 2 3/2 == 1 ``` 2. если i целое, то i + 0.0 - число с плавающей точкой с таким же математическим значением, как у целого. Правда, нужно помнить о скобках, если они необходмы, но, в принципе, проблему можно считать решенной. В случае констант 2 - целое, а 2.0 и даже просто 2. - число с плавающей точкой. Поэтому все приведенные ниже варианты работают: ``` int two=2 ; 3/(two + 0.0) == 1.5 3/(2 + 0.0) == 1.5 3/2.0 == 1.5 3/2. == 1.5 ``` Можно использовать и приведение типов: ``` 3/(double) two == 1.5 3/(double) 2 == 1.5 ``` Правило 5. Используйте тип double вместо float, а для промежуточных вычислений не повредит и тип long douЬle. ### Сравнение чисел без знака. ``` #include <stdio.h> int main() { int neig = -2; size_t zero = 0; if (neig < zero) { printf("Да, -2 меньше 0"); } else printf("Нет, -2 не меньше 0"); } ``` Этот пример показывает, что в большинстве операций сравнения целых со знаком и без знака С принудительно приводит знаковый тип к беззнаковому. ### Безопасное преобразование строки в число Существует несколько функций для разбора текстовых строк с целью выделения числа. Самые популярные - `atoi` и `atof`. ``` char twelve[ ] = "12" ; int х = atoi(twelve) ; char million[ ] = "le6" ; doble m = atof(million) ; ``` Но никакой проверки ошибок в них нет: если переменная twelve содержит строку "XII", то `atoi` ( twelve ) вернет 0, и программа продолжит работу. Более безопасны функции `strtol` и `strtod`. ### Вырашивание устойчивых и плодоносяших макросов 1. Скобки! Очень легко получить не то, что ожидаешь, после того как макрос вставит какой-то текст. ``` #define double(х) 2*х ///Нужиы дополиительные скобки ``` Если теперь пользователь напишет `double(1+1)*8`, то в результате расширения макроса получится 2*1+1*8, что дает 10, а не 32. Чтобы все было правил1,но,нужно добавить скобки: ``` #define double( х ) (2*(х)) ``` Общее правило состоит в том, чтобы заключать в скобки все входные параметры , если нет особых причин поступить иначе. Для макросов, расширяющихся в выражение, сам результат макроса также нужно заключать в скобки. 2. Избегайте двойной подстановки. ``` #define max(a, b) ((a) > (b) ? (a) : (b)) ``` Написав ``` int x=1 , у=2 ; int m=max(х, у++) ``` пользователь ожидает, что m будет равно 2 (значение у до инкремента), после чего у примет значение 3. Но макрос-то расширяется следующим образом: ``` m = ((х) > (у++) ? (х) : (у++)) ``` а это означает, что у++ вычисляется дважды, то есть инкремент выполняется два раза, хотя пользователь ожидал однократного, и в результате m будет равно 3, а не 2. 3. Фигурные скобки для блоков. ``` #define doubleincrement(а, b) \\ Нужны фигурные скобки. (а)++; \ (b)++; ``` Он сделает совершенно не то, что нужно, если поставить его после i f: ``` int x=1, у=0 ; if(х > у) doubleincrement(x, у) ; ``` Если добавить отступы, чтобы сделать ошибку очевидной, то мы увидим такой результат расширения: ``` int x=1, у=0 ; if(х > у) (х)++; (у)++; ``` ``` #define doubleincrement(а, b) { \ (а)++; \ (b)++; \ } if(а > b) doubleincrement(a, b) ; else return 0; ``` расширяется в: ``` if(а > b) { (а)++; (b)++; } ; else return 0; ``` Лишняя точка с запятой перед else смущает компилятор. Типичное решение - обернуть макрос в однократно исполняемый цикл do while ``` #define doubleincrement(а, b) do { \ (а)++; (b)++; } while(0) ``` ### Защита заголовков Получить ошибку из-за двойного включения заголовков. В начало файла, который должен включаться только один раз, помещается строка: ``` #pragma once ``` ### Компоновка с ключевыми словами `static` и `extern` Компилятор обрабатывает по одному C-файлу за раз и (обычно) порождает один O-файл. После этого компоновщик связывает все O-файлы вместе, создавая библиотеку или исполняемый файл. Что случится, если в двух разных файлах встретятся объявления одной и той же переменной х? Быть может, автор одного файла не знал, что автор другого уже выбрал имя х, так что на самом деле эти две х должны были бы находиться в разных пространствах имен? А быть может, оба автора прекрасно знали, что ссылаются на одну и ту же переменную, и компоновщик должен сделать так, чтобы все ссылки на х вели на одну и ту же область памяти. Под внешней компоновкой понимается, что одинаковые символы, определенные в разных файлах, должны рассматриваться компоновщиком как один символ. Для обозначения внешней компоновки применяется ключевое слово extern. Говоря о внутренней компоновке, имеют в виду, что экземпляр переменной х или функции f() в некотором файле принадлежит этому файлу и может быть отождествлен только с другими экземплярами х или f() в той же области видимости (что для объектов, объявленных вне функции, означает область видимость файла). Для обозначения внешней компоновки применяется ключевое слово static. Для переменных в области видимости файла слово static влияет только на компоновку: - по умолчанию подразумевается внешняя компоновка, поэтому для изменения ее на внутреннюю нужно добавить ключевое слово static; - для любой переменной в области видимости файла применяется статическая модель памяти вне зависимости от того, написано static int х, extern int х или просто int х. Для переменных в области видимости блока слово static влияет тол ько на модель памяти: - по умолчанию подразумевается внутренняя компоновка, поэтому ключевое слово static на компоновку не влияет. Компоновку можно изменить, добавив в объявление переменной слово extern, но так делают редко; - по умолчанию модель памяти автоматическая, а наличие ключевого слова static изменяет ее на статическую. Для функций слово static влияет только на компоновку: - функции определяются только в области видимости файла (хотя gcc поддерживает вложенные функции как расширение). Как и в случае переменных в области видимости файла, по умолчанию подразумевается внешняя компоновка, а наличие ключевого слова static меняет ее на внутреннюю; - путаницы с моделями памяти не возникает, потому что функции всегда статические, как и переменные в области видимости файла. Обычно, чтобы сделать функцию доступной в нескольких с-файлах, ее объявление помещают в h-файл, а определение - в какой-нибудь один с-файл (где у нее по умолчанию будет внешняя компоновка). ## Текст Составленная из букв строка - это массив неопределенной длины, а размер автоматически выделенного в стеке массива нельзя изменить, и в этом корень всех проблем с текстом в С. ### Безболезенная обработка строк с помошью `asprintf` Функция `asprintf` выделяет для строки столько памяти, сколько нужно, а затем заполняет ее. Это означает, что вам думать о выделении памяти для строк больше не нужно. ``` #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> void get_strings(char const *in) { char *cmd; asprintf(&cmd, "strings %s", in); if (system(cmd)) fprintf(stderr, "что то не так с командой %s\n", cmd); } int main(int argc, char **argv) { get_strings(argv[0]); } ``` ### Безопасность Если имеется строка фиксированной длины str и вы записываете в нее данные неизвестной длины с помощью sprintf, то можете случайно затереть область по соседству с str - классическое нарушение безопасности. Поэтому вместо sprintf рекомендуется применять функцию snprintf, которая ограничивает длину записываемых данных. Заранее проверяйте, что строки из не заслуживающих доверия источников имеют разумную длину. ### Константные строки Разница между константной и изменяемой строкой тонка и провоцирует ошибки, поэтому зашитые в код строки полезны только в очень специальных контекстах. Однако есть одно простое решение: функция strdup (string duplicate). Не стесняйтесь пользоваться strdup - и вы сможете обращаться со всеми строками одинаково, не думая о том, какие из них константные, а какие выделены динамически. ### Расширение строк с помощью asprintf Простейший пример добавления текста в существующую строку с помощью функции asprintf: ``` asprintf(&q , "%s и еще фраза %s", q, addme); ``` Такую технику применяют для построения запросов к базе данных. ``` int col_number = З, person_number = 27; char *q = strdup("select"); asprintf(&q, "%scol%i \n", q, col_number); asprintf(&q, "%sfrom tab \n", q); asprintf(&q, "%swhere person_id = %i", q, person_number); ``` ## Ключевое слово const Этот квалификатор типа показывает, что данные, на которые ведет параметр-указатель, не будут изменены внутри функции. Первый подвох: компилятор не защищает данные, на которые ведет указатель, от любых модификаций. Данные, помеченные квалификатором `const` под одним именем, можно модифицировать по другому имени. ``` #include <stdio.h> void set_element(int *a, int const *b) { a[0] = 3; } int main() { int a[10] = {}; int const *b = a; set_element(a, b); printf("b = %d", b[0]); } ``` Мы можем модифицировать данные через `а`, хотя `b` - const ### Форма существительное-прилагательное Объявления следует читать справа налево. Таким образом: ``` int const - константное целое int const * - (неконстантный) указатель на константное целое; int * const - константный указатель на (неконстантное) целое; int * const * - указатель на константный указатель на целое; int const * * - указатель на указатель на константное целое; int const * const * - указатель на константный указатель на константное целое. ``` #### Глубина Допустим, имеется структура - counter_ s - и функция, которая принимает указатель на эту структуру: f(counter_s const *in ) . Может ли такая функция изменять элементы структуры? ``` #include <assert.h> #include <stdlib.h> typedef struct { int *counter1, *counter2; } counter_s; void check_counter(int *ctr) { assert(*ctr != 0); } double ratio(counter_s *in) { check_counter(in->counter2); return *in->counter1 / (*in->counter2 + 0.0); } int main() { counter_s cc = {.counter1 = malloc(sizeof(int)), .counter2 = malloc(sizeof(int))}; *cc.counter1 = *cc.counter2 = 1; ratio(&cc); } ``` #Текст Задача о разбиении на лексемы встречается настолько часто, что в стандартной библиотеке С для нее есть специальная функция, strtok(string tokenize) strtok просматривает строку, пока не найдет первый разделитель, после чего записывает на его место '\0'. Теперь от строки осталась часть, представляющая первую лексему, и strtok возвращает указатель на ее начало. Внутри себя функция хранит информацию об исходной строке, поэтому при следующем вызове она будет искать конец следующей лексемы, заменит очередной разделитель нулем и вернет указатель на начало найденной лексемы. Начало каждой подстроки - это указатель на позицию внутри уже имеющейся строки, поэтому процесс разбиения сопровождается минимумом операций записи (только символы \0) и не требует никакого копирования. Правда, входная строка модифицируется, и поскольку подстроки представляют собой указатели внутрь исходной строки, то исходную строку нельзя освободить, пока не будет закончена работа с подстроками (либо можно с помощью функции strdup копировать лексемы в другое место по мере их выделения). ``` #include <string.h> #include <stdio.h> int main() { char text[] = "Проверка разбиения строки"; char *token; token = strtok(text, " "); do { printf("%s\n", token); } while (token = strtok(NULL, " ")); } ``` функция для подсчета строк ``` #include <string.h> //strtok_r int count_lines(char *instring) { int counter = 0; char *scratch, *txt, *delimiter = "\n"; while ((txt = strtok_r(!counter ? instring : NULL, delimiter, &scratch))) counter++; return counter; } ``` ## Улучшенная структура Позиционные инициализаторы существенно упрощают работу со структурами. Вместо непонятной и чреватой ошибками конструкции `person_struct р = { "Joe", 22, 75, 20};` мы можем писать самодокументированные объявления вида `person_struct р = {.name = "Joe", .age=22, .weight_kg=75, .education_years=20}` . ## Составные литералы Передать функции литеральное значение ничего не составляет: если имеется объявление `double a_value`, то С прекрасно понимает, что такое `f(a_value )`. Но если вы хотите передать список элементов - составное литеральное значение вида `{20.38, а_value, 9.8}` , - то возникает синтаксическое неудобство: требуется поставить перед составным литералом оператор приведения типа, иначе анализатор не поймет, что имеется в виду. Теперь список выглядит как ``` (double[]){20.38, а_value, 9.8} ``` обращение принимает вид: ``` f((double[]){20.38, а_value, 9.8}); ``` Память для составных литералов выделяется автоматически, то есть ни malloc, ни free не нужны. В конце области видимости, в которой появился составной литерал, он попросту исчезает. ### Беэопасное эавершение списков ``` #include <math.h> //NAN #include <stdio.h> double sum_array(double in[]){ double out=0; for (int i=0; !isnan(in[i]); i++) out += in[i]; return out; } /* макрос с переменным числом аргументов копирует свои аргументы в составной литерал. Таким образом, макрос принимает произвольное количество значений типа double, а передает их в виде одного списка, в конце которого гарантированно находится NaN. */ #define sum(...) sum_array((double[]){__VA_ARGS__, NAN}) int main(){ double two_and_two = sum(2, 2); printf("2+2 = %g\n", two_and_two); printf("(2+2)*3 = %g\n", sum(two_and_two, two_and_two, two_and_two)); printf("sum(asst) = %g\n", sum(3.1415, two_and_two, 3, 8, 98.4)); } ``` ### Векториэаuия функции Мы можем написать макрос с переменным числом аргументов, который генерирует массив (с завершающим маркером) с помощью составного литерала, а затем выполняет цикл for, в котором функция применяется к каждому элементу массива. ``` #include <stdio.h> #include <stdlib.h> #define Fn_apply(type, fn, ...) \ { \ void *stopper_for_apply = (int[]){0}; \ type **list_for_apply = (type *[]){__VA_ARGS__, stopper_for_apply}; \ for (int i = 0; list_for_apply[i] != stopper_for_apply; i++) \ fn(list_for_apply[i]); \ } #define Free_all(...) Fn_apply(void, free, __VA_ARGS__); int main() { double *x = malloc(10); double *y = malloc(100); double *z = malloc(1000); Free_all(x, y, z); } ``` ## Возврат нескольких значений из функции В С можно вернуть структуру, а стало быть, столько элементов, сколько необходимо. ``` #include <stdio.h> #include <strings.h> #include <math.h> typedef struct { double width, height; } size_s; size_s width_height(const char *papertype) { return !strcasecmp(papertype, "A4") ? (size_s){.width = 210, .height = 297} : !strcasecmp(papertype, "Letter") ? (size_s){.width = 216, .height = 279} : !strcasecmp(papertype, "Legal") ? (size_s){.width = 216, .height = 356} : (size_s){.width = NAN, .height = NAN}; } int main(int argc, char const *argv[]) { size_s paper_size = width_height(argv[1]); printf("Ширина = %g, высота = %g\n", paper_size.width, paper_size.width); } ``` Альтернативный способ - использование указателей - часто встречается и не считается дурным тоном, но при этом бывает трудно понять, какие параметры входные, а какие - выходные, так что вариант с дополнительным typedef стилистически выглядит предпочтительнее: ``` // И высота, и ширина возвращаются по указателям: void width_height(char *papertype, double *width, double *height); // Ширина возвращается непосредственно, а высота - по указателю: double width_height(char *papertype, double *height); ``` ## Гибкая передача аргументов функuиям Функции могут принимать переменное число аргументов (иногда такие функции называют вариадическими). Стоит помнить, что функции с переменным числом аргументов - небезопасные. Если функция ожидает получить три аргумента, а передано только два, то, скорее всего, дело закончится нарушением защиты памяти. В какой-то мере обезопасить функции с переменным числом аргументов одного и того же типа: написать макрос-обертку, который дописывает завершающий элемент в конец списка, гарантируя тем самым, что обернутая функция не получит незавершенного списка. Составной литерал проверяет также типы входных параметров и не будет компилироваться, если передан параметр не того типа. Первый способ основан на том, что компилятор умеет проверять аргументы printf, а второй - на использовании макросов с переменным числом аргументов для подготовки входных параметров к применению синтаксиса позиционных инициализаторов в заголовках функций. ### Объявление своей функции по аналогии с printf Чтобы сделать функцию с переменным числом аргументов, отвечающую стандарту С89, безопасной, понадобится расширение, ключевое слово `__attribute___`, открывающее доступ к различным зависящим от компилятора трюкам. Оно должно находиться в строке объявления переменной, структуры или функции (поэтому если ваша функция не определена до использования, то придется ее определить). ``` /* * export CFLAGS="-g -Wall -std=gnu11 -O3" */ #define _GNU_SOURCE // vasprintf #include <stdio.h> //printf, vasprintf #include <stdlib.h> //system #include <assert.h> #define System_w_printf(outval, ...) \ { \ char *string_for_systemf; \ asprintf(&string_for_systemf, __VA_ARGS__); \ outval = system(string_for_systemf); \ free(string_for_systemf); \ } int main(int argc, char const **argv) { assert(argc == 2); int out; System_w_printf(out, "ls %s", argv[1]); return out; } ``` ### Необязательные и именованные аргументы Структура во многих отношениях аналогична массиву, только позволяет хранить данные разных типов. А раз так, то можно применить ту же самую схему: написать макрос-обертку для упаковки элементов в структуру, а затем передать эту структуру функции. Макрос собирает воедино функцию, принимающую переменное число именованных аргументов. Определение функции состоит из трех частей: одноразовой структуры, к которой никогда не обращаются по имени (однако оно все равно засоряет глобальное пространство имен, если функция глобальна); макроса, который вставляет свои аргументы в структуру, которая затем передается обертываемой функции, и самой обертываемой функции. ``` #include <stdio.h> typedef struct { double pressure, moles, temp; } ideal_struct; #define ideal_pressure(...) ideal_pressure_base((ideal_struct){.pressure = 1, .moles = 1, .temp = 273.15, __VA_ARGS__}) double ideal_pressure_base(ideal_struct in) { return 8.314 * in.moles * in.temp / in.pressure; } int main() { printf("объем с параметрами по умолчанию:%g\n", ideal_pressure()); printf("oбъeм при температуре кипения:%g\n", ideal_pressure(.temp = 373.15)); printf("объем двух молей вещества:%g\n", ideal_pressure(.moles = 2)); printf("oбъeм двух молей вещества при температуре кипения:%g\n", ideal_pressure(.moles = 2, .temp = 373.15)); } ``` Общее правило состоит в том, что если некоторое поле инициализируется несколько раз, то предпочтение отдается последней инициализации. Поэтому поле `.pressure` будет иметь значение по умолчанию, тогда как остальные два входных параметра - значения, заданные пользователем. ## Указатель на void и структура, на которую он указывает ### Функции с обобщенными входными параметрами Функцией обратного вызова называется функция, которая передается другой функции, чтобы та ее вызывала в подходящий момент.