Увеличение размера памяти, выделенной структуре через Malloc - PullRequest
3 голосов
/ 15 сентября 2011

Я только что узнал, что можно увеличить размер памяти, которую вы будете выделять структуре при использовании функции malloc.Например, у вас может быть такая структура:

struct test{
    char a;
    int v[1];
    char b;
};

В которой явно есть место только для 2 символов и 1 int (указатель на int в реальности, но в любом случае) .Но вы можете вызвать malloc таким образом, чтобы структура содержала 2 символа и столько целых чисел, сколько вы хотели (скажем, 10):

int main(){
    struct test *ptr;
    ptr = malloc (sizeof(struct test)+sizeof(int)*9);
    ptr->v[9]=50;
    printf("%d\n",ptr->v[9]);   
return 0;
}

Вывод здесь будет отображаться как «50» на экране.Это означает, что массив внутри структуры содержал до 10 дюймов.

Мои вопросы к опытным программистам на C:

  1. Что происходит за кулисами здесь?Выделяет ли компьютер 2 + 4 (2 символа + указатель на int) байтов для стандартного "struct test", а затем еще 4 * 9 байт памяти и позволяет указателю "ptr" помещать любые данные, которые ему нужны, в эти дополнительныебайт?

  2. Этот трюк работает только тогда, когда внутри структуры есть массив?

  3. Если массив не является последним членом структуры,как компьютер управляет выделенным блоком памяти?

Ответы [ 3 ]

6 голосов
/ 15 сентября 2011

... Который явно имеет место только для 2 символов и 1 int (указатель на int в реальности, но в любом случае) ...

Уже неверно.Массивы не указатели.Ваша структура содержит пространство для 2 char с и 1 int.Там нет указателя какого-либо вида там.То, что вы объявили, по существу эквивалентно

struct test {
    char a;
    int v;
    char b;
};

Нет большой разницы между массивом из 1 элемента и обычной переменной (есть только концептуальная разница, то есть синтаксический сахар).

... Но вы можете вызвать malloc таким образом, чтобы он содержал 1 символ и столько целых, сколько вы хотели (скажем, 10) ...

Э-э ... Если вы хотите, чтобы он содержал 1 char, почему вы объявили свою структуру с 2 char s ???

В любом случае, чтобы реализовать массив гибкого размера какчлен структуры, вы должны поместить свой массив в самый конец структуры.

struct test {
    char a;
    char b;
    int v[1];
};

Затем вы можете выделить память для вашей структуры с некоторой «дополнительной» памятью для массива в конце

struct test *ptr = malloc(offsetof(struct test, v) + sizeof(int) * 10);

(Обратите внимание, как offsetof используется для вычисления правильного размера).

Таким образом, он будет работать, давая вам массив размером 10 и 2 char s в структуре (как заявлено).Он называется "взломом структуры" и критически зависит от того, является ли массив самым последним членом структуры.

В версии C99 языка C появилась специальная поддержка "struct hack".В C99 это можно сделать как

struct test {
    char a;
    char b;
    int v[];
};

...
struct test *ptr = malloc(sizeof(struct test) + sizeof(int) * 10);

Что здесь происходит за кулисами?Выделяет ли компьютер 2 + 4 (2 символа + указатель на int) байтов для стандартного "struct test", а затем еще 4 * 9 байт памяти и позволяет указателю "ptr" помещать любые данные, которые ему нужны, в эти дополнительныебайт?

malloc выделяет столько памяти, сколько вы просите выделить.Это всего лишь один плоский блок сырой памяти.Больше ничего не происходит "за кадром".В вашей структуре нет никакого "указателя на int" любого типа, поэтому любые вопросы, которые включают "указатель на int", вообще не имеют смысла.

Этот трюк работает только тогда, когда внутри находится массивstruct?

Ну, в этом весь смысл: получить доступ к дополнительной памяти, как если бы она принадлежала массиву, объявленному как последний член структуры.

Еслимассив не последний член структуры, как компьютер управляет выделенным блоком памяти?

Он ничего не управляет.Если массив не является последним членом структуры, то попытка работать с дополнительными элементами массива приведет к удалению членов структуры, объявленной после массива.Это довольно бесполезно, поэтому «гибкий» массив должен быть последним членом.

0 голосов
/ 15 сентября 2011

В дополнение к тому, что говорили вам другие (резюме: массивы не являются указателями, указатели не являются массивами, прочитайте раздел 6 comp.lang.c FAQ ), пытаясь получить доступ к элементам массива после последний элемент вызывает неопределенное поведение.

Давайте рассмотрим пример, который не включает динамическое распределение:

struct foo {
    int arr1[1];
    int arr2[1000];
};

struct foo obj;

Язык гарантирует, что obj.arr1 будет выделяться, начиная со смещения 0, и что смещение obj.arr2 будет sizeof (int) или более (компилятор может вставить заполнение между элементами структуры и после последнего члена, но не до первого). Итак, мы знаем, что в obj достаточно места для нескольких int объектов, следующих сразу за obj.arr1. Это означает, что если вы напишите obj.arr1[5] = 42, а затем получите доступ к obj.arr[5], вы , вероятно, вернете значение 42, которое вы там сохранили (и, вероятно, у вас будет забит obj.arr2[4] ).

Язык C не требует проверки границ массива, но делает поведение доступа к массиву вне его объявленных границ undefined . Может произойти все, что угодно, в том числе заставить код спокойно вести себя так, как вы этого хотите. Фактически, C разрешает проверку границ массива; он просто не обеспечивает способ обработки ошибок, и большинство компиляторов не реализуют его.

Для такого примера вы, скорее всего, столкнетесь с видимыми проблемами при наличии оптимизации. Компилятору (особенно оптимизирующему компилятору) разрешено предполагать , что поведение вашей программы хорошо определено, и переупорядочивать сгенерированный код, чтобы воспользоваться этим предположением. Если вы напишите

int index = 5;
obj.arr1[index] = 42;

компилятору разрешается предполагать, что операция индекса не выходит за заявленные границы массива. Как писал Генри Спенсер: «Если вы лжете компилятору, он отомстит».

Строго говоря, взлом структуры, вероятно, предполагает неопределенное поведение (именно поэтому C99 добавил четко определенную версию), но он настолько широко используется, что большинство или все компиляторы его поддерживают. Это описано в вопросе 2.6 comp.lang.c FAQ .

0 голосов
/ 15 сентября 2011

Нет, это не работает.Вы не можете изменить неизменяемый размер структуры (в конце концов, это распределение во время компиляции), используя malloc () во время выполнения.Но вы можете выделить блок памяти или изменить его размер так, чтобы он содержал более одной структуры:

int main(){
    struct test *ptr;
    ptr = malloc (sizeof(struct test) * 9);
}

Это почти все, что вы можете сделать с помощью malloc () в этом контексте.

...