Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Прочее

Массивы переменной длины в 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 не поддерживает впередиидущее объявление параметра.

Массивы переменной длины в C/C++
            А теперь разберёмся поподробнее. Коль массивы переменной длины открывают нам новые горизонты, попробуем их на деле:
 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-19    09:41

ОценитеОценки посетителей
   ████████████████████████████ 2 (66.6%)
   ██████████████ 1 (33.3%)
   ▌ 0
   ▌ 0

Отзывы

     2016/08/06 23:41, rst256

Макросы всегда помогут!
#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 ); })

Написать отзыв

Написать автору можно на электронную почту mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Каким должен быть язык программирования?

Анализ и критика

Устарел ли текст как форма представления программы

Русский язык и программирование

Многоязыковое программирование

Синтаксис языков программирования

Синтаксический сахар

Некоторые «вкусности» Алгол-68

«Двухмерный» синтаксис Python

Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

Должна ли программа быть удобочитаемой?

Стиль языка программирования

Тексто-графическое представление программы

●  Разделители

●  Строки программы

●  Слева направо или справа налево?

Комментарии

●  Длинные комментарии

●  Короткие комментарии

●  Комментарии автоматической генерации документации

●  Нерабочий код

Нужны ли беззнаковые целые?

Шестнадцатиричные и двоичные константы

Условные операторы

Переключатель

Циклы

●  Продолжение цикла и выход из него

Некошерный «goto»

Операции присвоения и проверки на равенство. Возможно ли однаковое обозначение?

Так ли нужны операции «&&», «||» и «^^»?

Постфиксные инкремент и декремент

Почему в PHP для конкатенации строк используется «.»?

Указатели и ссылки в C++

Использование памяти

Почему динамическое распределение памяти – это плохо

Как обеспечить возврат функциями объектов переменной длины?

●  Типы переменного размера (dynamically sized types, DST) в языке Rust

●  Массивы переменной длины в C/C++

●  Размещение объектов в стеке, традиционный подход

●  Размещение объектов переменной длины с использованием множества стеков

●  Размещение объектов переменной длины с использованием двух стеков

●  Реализация двухстековой модели размещения данных

●  Двухстековая модель: тесты на скорость

●  Размещение объектов переменной длины с использованием одного стека

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

Безопасность и размещение объектов переменной длины в стеке

Массивы, структуры, типы, классы переменной длины

О хранении данных в стеке, вместо заключения

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Прочее

Последние комментарии

2018/10/11 22:29, Автор сайта
Формула расчета точности для умножения

2018/10/08 14:00, Неслучайный читатель
Сколько проходов должно быть у транслятора?

2018/10/06 12:19, Автор сайта
Тексто-графическое представление программы

2018/10/04 17:39, Автор сайта
Об исключенных командах или за что «списали» инструкцию INTO?

2018/09/29 16:52, Автор сайта
Как отличить унарный минус от бинарного

2018/09/22 20:13, Д.Ю.Караваев
Идеальный транслятор

2018/09/22 12:32, Автор сайта
Типы в инженерных задачах

2018/09/22 12:20, Д.Ю.Караваев
О русском языке в программировании