Массивы переменной длины в C/C++
Одно из нововведений стандарта ISO C99 — автоматические
массивы переменной длины .
Коротко процитируем:
Они так же они приняты в качестве расширения в GCC.
Эти массивы объявляются подобно другим массивам, размещаемым в стеке,
но их длина не является константным выражением.
Память под такие массивы выделяется в точке объявления и изымается
при выходе из области видимости, содержащей объявление. Например:
FILE * concat_fopen (char *s1, char *s2, char *mode) {
char str[strlen (s1) + strlen (s2) + 1];
strcpy (str, s1);
strcat (str, s2);
return fopen (str, mode);
}
Выход из области видимости имени массива изымает память.
Вход в область видимости невозможен, в этом случае компилятор сообщит об ошибке.
В качестве расширения в GCC возможны массивы переменной длины в качестве
членов структур и объединений. Например:
void foo (int n) {
struct S {
int x[n];
};
}
Вы можете пользоваться функцией «alloca» для получения эффекта, подобному массивам переменной длины. Функция «alloca» есть во многих других компиляторах C, Но не во всех. Этой функции нет старых компиляторах, например, Visual C++ 6.0, и в некоторых относительно свежих, например TinyCC. Тем не менее, массивы переменной длины — элегантное решение.
Есть некоторая разница между выделением памяти под массивы переменой длины и выделением памяти функцией «alloca». Память, выделенная с помощью «alloca», существует до оператора «return». Память для массива переменной длины изымается сразу по окончании области видимости.
Так же можно использовать массивы переменной длины как аргументы функций:
struct entry tester (int len, char data[len][len]) {
/* ... */
}
Длина массива вычисляется единожды в момент выделения памяти.
В том случае, когда её хотят узнать с помощью «sizeof», она запоминается.
Если вы хотите разместить массив первым в списке параметров,
а его длину потом, можете использовать впередиидущее объявление в списке параметров —
это ещё одно расширение GNU.
struct entry tester (int len; char data[len][len], int len) {
/* ... */
}
«int len» перед точкой с запятой —
это впередиидущее объявление параметра, назначение которого —
сделать известным имя len для синтаксического анализа объявления data.
Можно записывать любое число таких впередиидущих объявлений
параметров в списке параметров. Они могут быть отделены запятыми или точками с запятой, но последнее должно заканчиваться точкой с запятой, за которой следуют настоящее объявление параметры. Каждое впередиидущее объявление должно соответствовать настоящему объявлению имени параметра и типу данных. Стандарт ISO C99 не поддерживает впередиидущее объявление параметра.
|
А теперь разберёмся поподробнее.
Коль массивы переменной длины открывают нам новые горизонты, попробуем их на деле:
FILE * concat_fopen (char *s1, char *s2, char *mode) {
char str[strlen (s1) + strlen (s2) + 1];
strcpy (str, s1);
strcat (str, s2);
return fopen (str, mode);
}
MESSAGE * concat_message (char *s1, char *s2) {
char str[strlen (s1) + strlen (s2) + 1];
strcpy (str, s1);
strcat (str, s2);
return message (str);
}
LOG * concat_log (char *s1, char *s2) {
char str[strlen (s1) + strlen (s2) + 1];
strcpy (str, s1);
strcat (str, s2);
return log (str);
}
Что-то много мышиной возни в этом коде.
Мы же не на ассемблере пишем, а на языке высокого уровня,
а тут уровень языка какой-то средненький.
Три вышеприведённые функции более чем наполовину состоят из одного того же.
Надо выделить повторяющие части в отдельную функцию.
Вся суть программирования заключается в том, чтобы увидеть какие-то
общие черты и выделить их во что-то отдельное.
Так повышается уровень абстракции.
Общие для трёх функций строки
char str[strlen (s1) + strlen (s2) + 1];
strcpy (str, s1);
strcat (str, s2);
сделаем отдельной функцией:
char* smartstrcat(char *s1, char *s2) {
char str[strlen (s1) + strlen (s2) + 1];
strcpy (str, s1);
strcat (str, s2);
return str;
}
Это даст возможность написать более абстрактный и умный код:
fopen (smartstrcat(s1, s2), mode);
message (smartstrcat(s1, s2));
log (smartstrcat(s1, s2));
Не правда ли, этот код элегантнее? Увы, он не будет работать.
Дело в том, наша функция «smartstrcat» хранит строку str локально, в своём стеке.
После «return» строка оказывается за пределами стека той функции,
которая вызвала нашу «smartstrcat».
Эта функция не гарантирует сохранность данных,
они могут быть потёрты любым другим вызовом функции.
Время жизни локальных данных функции неразрывно связано со временем жизни самой функции.
Но где тогда разместить строку? В динамической памяти?
Но это затратно!
Может, нам придут на выручку макросы?
Но макросы — довольно таки примитивная вещь.
Они ничего не ведают о типизации.
Если обычную функцию можно передавать в качестве параметра
другим функциям и возвращать из функций, то с макросом этого сделать нельзя.
Может, нам помогут inline-функции?
Это похоже на макросы, но более «культурно».
Но совсем не факт, кто ваш компилятор C/C++, вcтретив «inline», обязательно
сделает подстановку, а не вызов.
В создаваемом языке программирования можно сделать «inline» обязательным к исполнению,
в таком случае некоторые (но не все)
ограничения
снимаются.
Может быть, строку из приведённых примеров надо
сохранять в стеке вызывающей функции?
C/C++ так не умеет, но мы будем двигаться как раз
в этом направлении.
Какой вывод можно сделать из всего этого?
Массивы переменной длины и выделение памяти в локальном стеке с помощью «alloca» —
неплохая возможность, но ей присущи недостаточная гибкость и универсальность.
Читаем далее следующую статью:
Размещение объектов в стеке, традиционный подход.
Почитайте ещё:
Опубликовано: 2016.03.18, последняя правка: 2018.10.29 15:54
Отзывы
✅ 2016/08/06 23:41, rst256 #0
Макросы всегда помогут!#define smartstrcat(S1, S2) strcat( strcpy( alloca(strlen((S1)) + strlen((S2)) + 1), (S1)), (S2) ) Это не самый хороший вариант, т.к. аргументы в макросе применяются дважды. Т.е. в случае передачи ему выражения, например, smartstrcat(--s, "@dsfsdfd$"), оно будет вычислено дважды. Но расширение языка с в компиляторе GCC данную проблему легко решает, вот вариант для GCC:#define smartstrcat(S1, S2) ({ const char *s1=(S1), *s2=(S2); strcat( strcpy( alloca(strlen(s1) + strlen(s2) + 1), s1), s2 ); }) ✅ 2019/01/14 08:25, utkin #1
А если у Вас встретится выражение вида:smartstrcat(smartstrcat(S1, S2), S2) то Вы получите фееричные тормоза. Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|