Указатели на указатели в C - PullRequest
0 голосов
/ 10 ноября 2011

Я пытаюсь понять, как работают указатели на указатели, и я вышел с этим примером, и он прекрасно компилируется. Но когда он выполняется, я получаю ошибку сегментации. PS: я не хочу, чтобы f1() вернул 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);   
    assert(str == NULL);

    return 0;
}

int f1(char **str_)
{
    if ((*str_ = realloc(*str_, sizeof(char) * 12)) == NULL)
    {
        fprintf(stderr,"realloc() failed\n");
        exit(3);
    }

    (*str_) = "hello there";

    return 0;
}
  • Что не так с кодом?

Ответы [ 2 ]

9 голосов
/ 10 ноября 2011

Вам нужно включить больше предупреждений в вашем компиляторе или получить лучший компилятор.Когда я компилирую ваш код, я получаю предупреждения:

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 * достаточно для объяснения вашей аварии - однако это не единственноепроблема с кодом.Фактически, есть также:

  1. зарождающаяся утечка памяти,
  2. фактическая утечка,
  3. место, где вы (случайно) полагаетесь на неопределенное поведение,и
  4. место, где вы полагаетесь на поведение, определяемое реализацией, которое может не соответствовать вашим ожиданиям.

Ваш код аннотирован:

#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;
}

Обсуждая этив пронумерованной последовательности:

  1. Начинается утечка, возникающая при перераспределении памяти.*str является единственным местом, где хранится предыдущее значение указателя на выделенную память, и если realloc() не удается, он возвращает 0 (нулевой указатель), но не освобождает старую память, но у вас больше нет указателяв старую память.

    Исправление:

    char *new_mem = realloc(*str, 12);
    if (new_mem == 0)
        ...error handling...
    *str = new_mem;
    

    Правило большого пальца : не присваивать возвращаемое значение из realloc() переменной, которая является ее первойАргумент.

  2. Фактическая утечка возникает потому, что вы назначаете указатель на строковую константу поверх указателя на вновь выделенную память.Самое простое решение - использовать 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() неверной), даже если она требует вычисления длины во время выполнения вместо вычисления времени компиляциикак показано.

  3. Неопределенное поведение возникает из-за утечки памяти в (2).Вы пытаетесь realloc() строковая константа, а не указатель, возвращаемый realloc().Это приводит к неопределенному поведению;он может аварийно завершиться (потому что realloc() пытается записать управляющую информацию в постоянную память) или может просто испортить систему памяти, что приведет к аварийному завершению через некоторое время.

    Исправление для этого - исправлениедля элемента (2).

  4. Поведение, определяемое реализацией, возникает из-за того, что стандарт C гласит:

    §7.20.3 Функции управления памятью ¶1: [...] Если размер запрошенного пространства равен нулю, поведение определяется реализацией: либо возвращается нулевой указатель, либо поведение такое, как если бы размер был некоторым ненулевым значением, за исключением того, что возвращаемое значениеуказатель не должен использоваться для доступа к объекту.

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

Итак, в коде всего 5 проблем, только одна из которых компилятор может вам помочь.

6 голосов
/ 10 ноября 2011

Первая строка main() устанавливает переменную str в NULL и передает указатель на нее в f1.

f1 работает отлично.Результат f1 заключается в том, что переменная str внутри main теперь является указателем на пробел в памяти, содержащий строку (литерал) "hello there".

Ваша следующая строка, printf, segfaults.Зачем?Потому что вы пытаетесь напечатать *str (обратите внимание на звездочку здесь !!) в виде строки (спецификатор формата %s).Что такое *str, когда оно интерпретируется как строка?Какой бы «адрес» не обозначался как «ад», вероятно (по крайней мере, на 32-битной машине).Сомнительно, что адрес находится в вашем пространстве процессов.

Классический segfault.

Попробуйте передать str вместо *str в printf.

Это сработает, см.http://codepad.org/Mh00txen

Существуют и другие проблемы с кодом, например, realloc из 12 символов в f1 ничего не делает, кроме как вызывает утечку памяти, потому что вы немедленно переназначаете этот указатель, чтобы он указывал на строковый литерал, но это непричина вашей ошибки.

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