Язык C: Почему динамически распределяемые объекты возвращают указатель, в то время как статически распределяемые объекты дают вам выбор? - PullRequest
5 голосов
/ 14 октября 2011

Это на самом деле гораздо более краткий, гораздо более понятный вопрос, чем тот, который я задавал здесь ранее (для всех, кому не все равно): C Язык: почему malloc () возвращает указатель, а не значение? (Извините за тех, кто изначально думает, что я рассылаю спам ... Я надеюсь, что это не истолковано как тот же вопрос, поскольку я думаю, что то, как я его сформулировал, сделало его непреднамеренно вводящим в заблуждение)

-> По сути, чтоЯ пытаюсь спросить: зачем программисту C указатель на динамически размещаемую переменную / объект?(какая бы разница ни была между переменной / объектом ...)

Если у программиста на С есть возможность создать просто 'int x' или просто 'int * x' (оба статически размещены), то почемуУ него также есть возможность ПРОСТО инициализировать свою динамически назначаемую переменную / объект как переменную (и НЕ возвращать указатель через malloc ())?

* Если есть некоторые неясные способы сделать то, что я объяснил вышетогда, ну почему же malloc () выглядит так, как в большинстве учебников идет динамическое распределение?

Ответы [ 7 ]

9 голосов
/ 14 октября 2011

Примечание: в дальнейшем byte относится к sizeof(char)

Ну, например, malloc возвращает void *.Он просто не может вернуть значение: это было бы невозможно из-за отсутствия универсальных символов в Си.В Си компилятор должен знать размер каждого объекта во время компиляции;поскольку размер выделяемой памяти не будет известен до времени выполнения, тогда должен быть возвращен тип, который может представлять любое значение.Поскольку void * может представлять любой указатель, это лучший выбор.

malloc также не может инициализировать блок: он не знает, что выделяется.Это в отличие от C ++ operator new, который выполняет как выделение, так и инициализацию, а также является безопасным типом (он по-прежнему возвращает указатель вместо ссылки, вероятно, по историческим причинам).

Также,malloc выделяет блок памяти определенного размера, затем возвращает указатель на эту память (вот что означает malloc: выделение памяти ).Вы получаете указатель, потому что это то, что вы получаете: унифицированный блок сырой памяти.Когда вы делаете, скажем, malloc(sizeof(int)), вы не создаете int, вы выделяете sizeof(int) байтов и получаете адрес этих байтов.Затем вы можете решить использовать этот блок как int, но вы также можете технически использовать его как массив sizeof(int) char с.

Различные альтернативы (calloc, realloc) работают примерно так же (calloc легче использовать при работе с массивами и заполнять нулями данные, тогда как realloc полезно, когда вам нужно изменить размер блока памяти).

1 голос
/ 14 октября 2011

Это потому, что объекты, выделенные с помощью malloc(), не имеют имен, поэтому единственный способ ссылаться на этот объект в коде - использовать указатель на него.

Когда вы говорите int x;, это создает объект с именем x, и на него можно ссылаться через это имя. Когда я хочу установить x на 10, я могу просто использовать x = 10;.

Я могу также установить переменную указателя для указания на этот объект с помощью int *p = &x;, а затем я могу альтернативно установить значение x с помощью *p = 10;. Обратите внимание, что на этот раз мы можем говорить о x, не называя его конкретно (за пределами того места, где мы получаем ссылку на него).

Когда я говорю malloc(sizeof(int)), это создает объект, у которого не имеет имени . Я не могу напрямую установить значение этого объекта по имени, так как у него просто его нет. Однако я могу установить его, используя переменную-указатель, которая указывает на него, поскольку этот метод не требует именования объекта: int *p = malloc(sizeof(int));, за которым следует *p = 10;.

Теперь вы можете спросить: «Итак, почему я не могу сказать malloc дать объекту имя?» - что-то вроде malloc(sizeof(int), "x"). Ответ на это имеет два аспекта:

  • Во-первых, C просто не позволяет вводить имена переменных во время выполнения. Это просто основное ограничение языка;
  • Во-вторых, учитывая первое ограничение, имя должно быть исправлено во время компиляции: если это так, C уже имеет синтаксис, который делает то, что вы хотите: int x;.
1 голос
/ 14 октября 2011

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

Однако, если вы используете malloc, вы создаете объект в куче, область которого превышает тело функции. Вы можете вернуть указатель на это. Вы просто должны уничтожить его позже, иначе у вас будет утечка памяти.

0 голосов
/ 14 октября 2011

Я думаю, что ваш вопрос сводится к этому:

Если у программиста C есть возможность создать только int x или просто int *x (оба статически распределены)

Первый оператор выделяет память для целого числа. В зависимости от размещения оператора он может выделять память в стеке выполняемой в данный момент функции или выделять память в разделах программы .data или .bss (если это глобальная переменная или static переменная, либо в области видимости файла, либо в области функций).

Второй оператор выделяет память для указателя на целое число - он фактически не выделил память для самого целого числа. Если вы попытаетесь присвоить значение с помощью указателя *x=1, вы получите либо очень быстрое SIGSEGV нарушение сегментации , либо испортит какой-то случайный фрагмент памяти. C не обнуляет память, выделенную в стеке:

$ cat stack.c
#include <stdio.h>

int main(int argc, char *argv[]) {
    int i;
    int j;
    int k;
    int *l;
    int *m;
    int *n;
    printf("i: %d\n", i);
    printf("j: %d\n", j);
    printf("k: %d\n", k);
    printf("l: %p\n", l);
    printf("m: %p\n", m);
    printf("n: %p\n", n);
    return 0;
}
$ make stack
cc     stack.c   -o stack
$ ./stack
i: 0
j: 0
k: 32767
l: 0x400410
m: (nil)
n: 0x4005a0

l и n указывают на что-то в памяти - но эти значения являются просто мусором и, вероятно, не принадлежат адресному пространству исполняемого файла. Если мы будем хранить что-либо в этих указателях, программа, вероятно, умрет. Однако он может повредить несвязанные структуры, если они отображаются в адресное пространство программы.

m по крайней мере - указатель NULL - если вы попытаетесь записать его, программа наверняка умрет на современном оборудовании.

Ни один из этих трех указателей на самом деле не указывает на целое число. Память для этих целых чисел не существует. Память для указателей существует существует - и изначально заполнена значениями мусора, в данном случае.

Статья в Википедии о L-значениях - в большинстве своем слишком тупая, чтобы полностью ее рекомендовать - содержит одно замечание, которое представляло для меня довольно существенное препятствие, когда я впервые изучал C: В языках с присваиваемые переменные становится необходимым различать между R-значением (или содержимым) и L-значением (или местоположением) переменной.

Например, вы можете написать:

int a;
a = 3;

Сохраняет целочисленное значение 3 в той памяти, которая была выделена для хранения содержимого переменной a.

Если вы позже напишите:

int b;
b = a;

Принимает значение , сохраненное в памяти, на которую ссылается a, и сохраняет его в ячейке памяти, выделенной для b.

Те же операции с указателями могут выглядеть следующим образом:

int *ap;
ap=malloc(sizeof int);
*ap=3;

Первое назначение ap= сохраняет ячейку памяти в указателе ap. Теперь ap фактически указывает на некоторую память. Второе назначение, *ap=, сохраняет значение в этой ячейке памяти . Он вообще не обновляет указатель ap; он читает значение, хранящееся в переменной с именем ap, чтобы найти место в памяти для назначения.

Когда вы позже используете указатель, вы можете выбрать, какое из двух значений, связанных с указателем, использовать: либо фактическое содержимое указателя , либо значение, на которое указывает указатель:

int *bp;
bp = ap; /* bp points to the same memory cell as ap */

int *bp;
bp = malloc(sizeof int);
*bp = *ap; /* bp points to new memory and we copy
              the value pointed to by ap into the
              memory pointed to by bp */

Я обнаружил, что сборка гораздо легче, чем C, потому что я нахожу разницу между foo = malloc(); и *foo = value; сбивающей с толку. Я надеюсь, что нашел то, что вас смутило и серьезно надеюсь, что я не сделал это хуже.

0 голосов
/ 14 октября 2011
  1. Все работает с помощью указателей.«int x» - это просто удобство - кто-то где-то устал от манипулирования адресами памяти, и именно так родились языки программирования с понятными для человека именами переменных.

  2. Динамическое распределение - это ...динамичный.Вам не нужно знать, сколько места вам понадобится при запуске программы - до ее запуска.Вы выбираете, когда это сделать, а когда отменить.Это может потерпеть неудачу.Трудно справиться со всем этим, используя простой синтаксис статического размещения.

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

Есть способ сделать то, что вы просите, и он называется C ++.Здесь «MyInt x = 42;» - это вызов функции или два.

0 голосов
/ 14 октября 2011

Вы думаете о вещах неправильно.Дело не в том, что int x является статически распределенным, а malloc (sizeof (int)) является динамическим.Оба распределяются динамически.То есть они оба выделяются во время выполнения.Во время компиляции для них не зарезервировано пространство.Размер может быть статическим в одном случае и динамическим в другом, но распределение всегда динамическое.

Скорее, это то, что int x выделяет память в стеке, а malloc (sizeof (int)) выделяетпамять в куче.Память в куче требует наличия указателя для доступа к ней.На память в стеке можно ссылаться напрямую или с помощью указателя.Обычно вы делаете это напрямую, но иногда вы хотите перебрать его с помощью арифметики с указателями или передать его функции, для которой нужен указатель.

0 голосов
/ 14 октября 2011

Возможно, вы неправильно понимаете разницу между объявлением «int x» и «int * x».Первый выделяет память для значения типа int;второй - нет - он просто выделяет память для указателя.

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

int x = malloc(sizeof(int)); *x = 0;

Когда вы можете просто сделать:

int x = 0;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...