Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки 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.18, последняя правка: 2018.10.29    15:54

ОценитеОценки посетителей
   ████████████████████████ 4 (57.1%)
   ██████ 1 (14.2%)
   ▌ 0
   ████████████ 2 (28.5%)

Отзывы

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

     2019/01/14 08:25, utkin          # 

А если у Вас встретится выражение вида:
smartstrcat(smartstrcat(S1, S2), S2)
то Вы получите фееричные тормоза.

Добавить свой отзыв

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

Авторизация

Регистрация

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

Карта сайта


Содержание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

●●  Помеченные комментарии

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

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

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

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

●  Циклы

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

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

●  Изменение приоритетов операций

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

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

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

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

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

●●  О неправомерном доступе к памяти через указатели

●  Обработка ошибок

●  Функциональное программирование

●●  Нечистые действия в чистых функциях

●●  О чистоте и нечистоте функций и языков

●●  Макросы — это чистые функции, исполняемые во время компиляции

●●  Хаскелл, детище британских учёных

●●  Измеряем замедление при вызове функций высших порядков

●●  C vs Haskell: сравнение скорости на простом примере

●●  Уникальность имён функций: за и против

●●  Каррирование: для чего и как

●●  О тестах, доказывающих отсутствие ошибок

●  Надёжные программы из ненадёжных компонентов

●●  О многократном резервировании функций

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

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

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

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

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

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

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

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

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

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

●●  Изменение длины объекта в стеке во время исполнения

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

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

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

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

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

●  Реализация параметрического полиморфизма

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

Компилятор

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

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

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

Новости и прочее




Последние отзывы

2024/04/18 11:14 ••• Ivan
Энтузиасты-разработчики компиляторов и их проекты

2024/04/18 04:47 ••• void
Признаки устаревшего языка

2024/04/11 00:08 ••• Автор сайта
Постфиксные инкремент и декремент

2024/04/09 23:50 ••• Автор сайта
Русский язык и программирование

2024/04/07 15:33 ••• MihalNik
Все языки эквивалентны. Но некоторые из них эквивалентнее других

2024/04/01 23:39 ••• Бурановский дедушка
Новости и прочее

2024/04/01 23:32 ••• Бурановский дедушка
Русской операционной системой должна стать ReactOS

2024/03/22 20:41 ••• void
Раскрутка компилятора

2024/03/20 19:54 ••• kt
О многократном резервировании функций

2024/03/20 13:13 ••• Неслучайный читатель
Надёжные программы из ненадёжных компонентов

2024/03/07 14:16 ••• Неслучайный читатель
«Двухмерный» синтаксис Python

2024/03/03 16:49 ••• Автор сайта
О неправомерном доступе к памяти через указатели

2024/02/28 18:59 ••• Вежливый Лис
Про лебедей, раков и щук