Как точно понять синтаксис двух функций malloc () и calloc () - PullRequest
4 голосов
/ 16 мая 2019

Я изучаю C и у меня есть вопрос о синтаксисе динамического выделения памяти.

Приведенный ниже код является примером динамического распределения памяти. Если я правильно понимаю

 (char *) malloc (50 * sizeof (char));

вернет указатель на тип данных char и

  pr = (char *) malloc (50 * sizeof (char));

назначит указатель 'pr' указателю, объявленному при выделении динамической памяти. Таким образом, у нас будет указатель на другой указатель

#include <stdio.h>
#include <stdlib.h>

int main()
{
   char *pr;

   pr = (char*)malloc( 50 * sizeof(char) ); 

   return 0;
}

Мне все еще трудно понять синтаксис, используемый для выделения динамической памяти. Может кто-нибудь объяснить подробно? спасибо

Ответы [ 3 ]

3 голосов
/ 16 мая 2019

назначит указатель 'pr' указателю, объявленному при выделении динамической памяти.Таким образом, у нас будет указатель на другой указатель

Нет, у вас будет сохранено значение переменной указателя.

Это то же самое, что и

int x = 5;

или, в более сложном случае

int test(void) { return 5;}
int x = test();

, в этом случае test() возвращает значение 5, которое присваивается x.Таким же образом

char * pr = malloc (50 * sizeof (*pr));  // no casting, and sizeof (*pr) is a
                                         // more robust way to have the size defined 

назначает возвращенный указатель с malloc() на pr.Здесь нет указателя на указатель.

Другими словами, pr содержит указатель, который указывает на область памяти, выделенную при вызове malloc(), или NULL (константа нулевого указателя) весли вызов не удался.

2 голосов
/ 16 мая 2019

Сначала давайте проясним немного терминологии:

Итак, у нас будет указатель на другой указатель

Это неверно.Когда вы назначаете указатель на другой указатель, второй указатель не указывает на первый указатель, он указывает на то же самое, что и первый указатель.Указание на указатель подразумевает два уровня разыменования, необходимых - в данном случае - чтобы добраться до char, то есть char a = **pr;

Итак, давайте посмотрим на код.

pr = (char*)malloc( 50 * sizeof(char) ); 

Прототип malloc равен

void *malloc(size_t size);

malloc, выделяет size байт памяти и возвращает указатель на этот блок памяти (или NULL, если он не может выделить блок памяти).malloc не имеет представления о том, на что вы хотите, чтобы он указывал, поэтому он решает вернуть void *, что означает указатель на что-то неизвестное.(char*) на передней панели вашего звонка на malloc является приведением типа.Он изменяет тип выражения по своему праву на тип, заключенный в скобки, в данном случае это char * (вы можете вставить или пропустить пробел между char и * без какого-либо эффекта).

Указатель, который теперь имеет тип char *, затем назначается на pr, который также имеет тип char *.

Единственное, что C будет выполнять приведение автоматически, если оно от void * и до другого типа указателя.Так что то, что вы написали, в точности эквивалентно

pr = malloc( 50 * sizeof(char) );

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

Раньше также существовала опасность того, что, если вы забудете #include <stdlib.h>, компилятор предположит, что malloc вернет int Если вы оставили приведение, компилятор пометит попытку приведения из int до char * как ошибка, но если вы вставите ее, ошибка будет подавлена.Эта проблема больше не существует, потому что C11 запрещает использование функции, которая не была объявлена.

Существует еще одна проблема, аналогичная описанной выше.Если у вас есть

char* pr;
// Lots of code
pr = malloc( 50 * sizeof(char) );
pr[49] = 0; 

и вы решите, что pr действительно должен указывать на int, вы можете получить

int* pr;
// Lots of code
pr = malloc( 50 * sizeof(char) );
for (int i = 0 ; i < 50 ; ++i)
{
    pr[i] = 0; // buffer overflow!
} 

, который будет компилироваться, но это неопределенное поведение, потому чтовыделенный блок недостаточно велик, чтобы вместить 50 int с.Вы можете решить это, вернувшись к явному приведению (int* pr = (char*)malloc(...) - ошибка), но лучший способ - указать sizeof для разыменованного указателя

int* pr;
// Lots of code
pr = malloc( 50 * sizeof *pr );
for (int i = 0 ; i < 50 ; ++i)
{
    pr[i] = 0; // OK if malloc returns non NULL, otherwise undefined behaviour, probably a SIGSEV
} 

calloc похоже на malloc за исключением того, что вместо того, чтобы указывать абсолютное количество байтов, вы указываете количество вещей, на которые указывают, и размер предметов, на которые указывают отдельно.calloc также обнуляет байты в выделенном блоке.

int *pr = calloc(50, sizeof *pr);

фактически совпадает с предыдущими битами кода, за исключением того, что это не неопределенное поведение, если память не может быть выделена.


NB: Многие люди говорят, что вы абсолютно не должны вставлять явное приведение, но на самом деле есть аргументы в обоих направлениях.Также это .

2 голосов
/ 16 мая 2019

Когда вы вызываете malloc(X), система пытается выделить X байтов и возвращает указатель на первый байт (если распределение было успешным, в противном случае возвращается NULL).

Когда вы вызываете calloc(X, Y), система пытается выделить X * Y байтов, а затем обнуляет все биты этой памяти. По сути это эквивалентно вызову malloc(X * Y), за которым следует memset.

Я также рекомендую вам прочитать Должен ли я приводить результат malloc? Что действительно можно суммировать как: Нет.

...