Сначала давайте проясним немного терминологии:
Итак, у нас будет указатель на другой указатель
Это неверно.Когда вы назначаете указатель на другой указатель, второй указатель не указывает на первый указатель, он указывает на то же самое, что и первый указатель.Указание на указатель подразумевает два уровня разыменования, необходимых - в данном случае - чтобы добраться до 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: Многие люди говорят, что вы абсолютно не должны вставлять явное приведение, но на самом деле есть аргументы в обоих направлениях.Также это .