Вам нужно включить больше предупреждений в вашем компиляторе или получить лучший компилятор.Когда я компилирую ваш код, я получаю предупреждения:
mem.c: In function ‘main’:
mem.c:12: warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘int’
mem.c:12: warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘int’
mem.c: At top level:
mem.c:7: warning: unused parameter ‘argc’
mem.c:7: warning: unused parameter ‘argv’
Я не совсем уверен, почему предупреждение в строке 12 повторяется, но как GCC 4.2, так и 4.6.1 в MacOS X 10.7 выдают его дважды,Предупреждения о argc
и argv
являются хорошей причиной для использования int main(void)
, когда вы не используете аргументы командной строки;они не являются большой проблемой.
Предупреждение (на самом деле, ошибка) о %s
и int
против char *
достаточно для объяснения вашей аварии - однако это не единственноепроблема с кодом.Фактически, есть также:
- зарождающаяся утечка памяти,
- фактическая утечка,
- место, где вы (случайно) полагаетесь на неопределенное поведение,и
- место, где вы полагаетесь на поведение, определяемое реализацией, которое может не соответствовать вашим ожиданиям.
Ваш код аннотирован:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int f1(char **str_);
int main(int argc, char **argv)
{
char *str = NULL;
f1(&str);
printf("str : %s\n", *str);
str = realloc(str, (size_t) 0); /* (3) Undefined behaviour */
assert(str == NULL); /* (4) Implementation-defined behaviour */
return 0;
}
int f1(char **str_)
{
if ((*str_ = realloc(*str_, sizeof(char) * 12)) == NULL) /* (1) Incipient leak */
{
fprintf(stderr,"realloc() failed\n");
exit(3);
}
(*str_) = "hello there"; /* (2) Actual leak */
return 0;
}
Обсуждая этив пронумерованной последовательности:
Начинается утечка, возникающая при перераспределении памяти.*str
является единственным местом, где хранится предыдущее значение указателя на выделенную память, и если realloc()
не удается, он возвращает 0 (нулевой указатель), но не освобождает старую память, но у вас больше нет указателяв старую память.
Исправление:
char *new_mem = realloc(*str, 12);
if (new_mem == 0)
...error handling...
*str = new_mem;
Правило большого пальца : не присваивать возвращаемое значение из realloc()
переменной, которая является ее первойАргумент.
Фактическая утечка возникает потому, что вы назначаете указатель на строковую константу поверх указателя на вновь выделенную память.Самое простое решение - использовать strcpy()
, хотя вам нужно добавить #include <string.h>
, когда вы это сделаете.Вы также, как правило, убедитесь, что вы выделяете достаточно места для строки, которую собираетесь скопировать, что приводит к:
char const hello[] = "hello there";
char *new_mem = realloc(str, sizeof(hello));
//char *new_mem = realloc(str, strlen(hello)+1);
if (new_mem == 0)
...handle error...
*str = new_mem;
strcpy(new_mem, hello);
В этом примере я могу использовать sizeof(hello)
, потому что размер строкивключает нулевой терминатор, и потому что фактическое определение массива находится в области видимости.Если копируемая строка была передана в функцию в качестве указателя, то альтернатива, использующая strlen(hello)+1
, является правильной (а использование sizeof()
неверной), даже если она требует вычисления длины во время выполнения вместо вычисления времени компиляциикак показано.
Неопределенное поведение возникает из-за утечки памяти в (2).Вы пытаетесь realloc()
строковая константа, а не указатель, возвращаемый realloc()
.Это приводит к неопределенному поведению;он может аварийно завершиться (потому что realloc()
пытается записать управляющую информацию в постоянную память) или может просто испортить систему памяти, что приведет к аварийному завершению через некоторое время.
Исправление для этого - исправлениедля элемента (2).
Поведение, определяемое реализацией, возникает из-за того, что стандарт C гласит:
§7.20.3 Функции управления памятью ¶1: [...] Если размер запрошенного пространства равен нулю, поведение определяется реализацией: либо возвращается нулевой указатель, либо поведение такое, как если бы размер был некоторым ненулевым значением, за исключением того, что возвращаемое значениеуказатель не должен использоваться для доступа к объекту.
Вы утверждаете, что ваша реализация выберет первый вариант, но может выбрать второй.Исправление состоит в том, чтобы удалить необоснованное утверждение.
Итак, в коде всего 5 проблем, только одна из которых компилятор может вам помочь.