realloc говорит, что следующий размер недействителен

Вот мой код, который должен динамически выделять память для хранения строк:

#define _XOPEN_SOURCE 700

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

int main()
{
    char **path = NULL;

    path = (char **) malloc(sizeof(char *));

    for (int i = 0; i < 5; i++)
    {
            path[i] = strdup("TEST");
            path = (char **) realloc(path, sizeof(char *));
    }

    for (int i = 0; i < 5; i++)
    {
            printf("%s\n", path[i]);
    }

    return 0;
}

Приведенный выше код прерывается в строке, где я перераспределил память.

По моему мнению, память, выделенная для пути на первом malloc, указана ниже:

path -----> |   char *  | // path points to a character pointer which       inturn is pointing to nothing(Dangling).

Итак, на данный момент, когда программа запускала строку:

path = (char **) malloc(sizeof(char *));

path[0] в настоящее время находится в границах, и мы можем хранить начальный адрес одной и только одной строки в path[0], а path[1] должен быть вне границ в этот момент времени.

Итак, когда мы в первый раз вошли в цикл for, в котором мы сохраняем адрес строки в path[i], мы сможем сделать это так:

path[0] = strdup("TEST"); // is in bounds.

Чтобы сохранить другую строку, нам нужно больше памяти, на которую должен указывать путь. Итак, я сделал realloc в строке чуть ниже:

path = (char **) realloc(path, (char *));

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

path --------->|old char *|----->"TEST"
  |----------->|new char *|----->(Dangling) // Pointing Nowhere.

Итак, теперь path[1] также находится в пределах границ, и мы должны иметь возможность использовать это место в памяти. Поэтому я не могу понять, почему я получаю эту ошибку сегментации, когда запускаю свой код:

*** glibc detected *** ./dynamic: realloc(): invalid next size: 0x0000000000602010 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x75018)[0x7f743153e018]
/lib64/libc.so.6(+0x7ae96)[0x7f7431543e96]
/lib64/libc.so.6(realloc+0xfa)[0x7f74315441aa]
./dynamic[0x40067f]
/lib64/libc.so.6(__libc_start_main+0xe6)[0x7f74314e7bc6]
./dynamic[0x400569]
======= Memory map: ========
00400000-00401000 r-xp 00000000 00:27 14459340                           /home/xpansat/c/basic/dynamic
00600000-00601000 r--p 00000000 00:27 14459340                           /home/xpansat/c/basic/dynamic
00601000-00602000 rw-p 00001000 00:27 14459340                           /home/xpansat/c/basic/dynamic
00602000-00623000 rw-p 00000000 00:00 0                                  [heap]
7f742c000000-7f742c021000 rw-p 00000000 00:00 0 
7f742c021000-7f7430000000 ---p 00000000 00:00 0 
7f74312b2000-7f74312c8000 r-xp 00000000 fd:01 173                        /lib64/libgcc_s.so.1
7f74312c8000-7f74314c7000 ---p 00016000 fd:01 173                        /lib64/libgcc_s.so.1
7f74314c7000-7f74314c8000 r--p 00015000 fd:01 173                        /lib64/libgcc_s.so.1
7f74314c8000-7f74314c9000 rw-p 00016000 fd:01 173                        /lib64/libgcc_s.so.1
7f74314c9000-7f743161d000 r-xp 00000000 fd:01 27                         /lib64/libc-2.11.1.so
7f743161d000-7f743181d000 ---p 00154000 fd:01 27                         /lib64/libc-2.11.1.so
7f743181d000-7f7431821000 r--p 00154000 fd:01 27                         /lib64/libc-2.11.1.so
7f7431821000-7f7431822000 rw-p 00158000 fd:01 27                         /lib64/libc-2.11.1.so
7f7431822000-7f7431827000 rw-p 00000000 00:00 0 
7f7431827000-7f7431846000 r-xp 00000000 fd:01 20                         /lib64/ld-2.11.1.so
7f7431a14000-7f7431a17000 rw-p 00000000 00:00 0 
7f7431a44000-7f7431a45000 rw-p 00000000 00:00 0 
7f7431a45000-7f7431a46000 r--p 0001e000 fd:01 20                         /lib64/ld-2.11.1.so
7f7431a46000-7f7431a47000 rw-p 0001f000 fd:01 20                         /lib64/ld-2.11.1.so
7f7431a47000-7f7431a48000 rw-p 00000000 00:00 0 
7fff62f8c000-7fff62fa2000 rw-p 00000000 00:00 0                          [stack]
7fff62fff000-7fff63000000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Я не могу понять, что я делаю неправильно, потому что я смог запустить свой код, если изменил строку realloc, как показано ниже:

path = (char **) realloc(sizeof(char *)*8);

Если это умножение на 8, мой код запускается, но я хочу выделить именно ту память, которую хочу. Любые идеи, почему он не работает без умножения на 8.


person mSatyam    schedule 02.07.2013    source источник
comment
У вас где-то переполнение буфера. Похоже, вы используете Linux, поэтому просто запустите его под Valgrind, чтобы найти его.   -  person Jan Hudec    schedule 02.07.2013
comment
path = (char **) malloc(sizeof(char *));, затем path = (char **) realloc(path, sizeof(char *)); ‹- доступ к path[1] в for (int i = 0; i < 5; i++) является поведением undefined. Ты топчешь всю память, к которой не имеешь права доступа.   -  person Daniel Fischer    schedule 02.07.2013
comment
Обратите внимание, что realloc совершенно бессмысленно, потому что вы запрашиваете точно такой же размер, для которого изначально выделили массив. Помните, что sizeof(type) — это константа времени компиляции!   -  person Jan Hudec    schedule 02.07.2013
comment
Приведение malloc()/calloc()/realloc() в C не требуется, тем более не рекомендуется: stackoverflow.com/q/605845/694576   -  person alk    schedule 02.07.2013


Ответы (2)


Здесь

path = (char **) malloc(sizeof(char *));

вы выделяете указатель на указатель на char, то есть место для одного указателя на C-"строку".

Тогда здесь:

for (int i = 0; i < 5; i++)
{
   path[i] = strdup("TEST");
   path = (char **) realloc(path, sizeof(char *));
}

вы не только используете один указатель на C-"строку", но перераспределение большего количества не делает ничего, кроме перераспределения того же размера, что и раньше, но на самом деле вы получаете доступ, как если бы вы добавляли память для еще одного указателя в каждой итерации.

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

Несколько более прямым подходом к этому будет:

  ...

  char ** path = NULL;

  for (size_t i = 0; i < 5; ++i)
  {
    path = realloc(path, (i+1) * sizeof(*path));
    path[i] = strdup("TEST");
  }

  ...
person alk    schedule 02.07.2013
comment
привет, на самом деле я не понимаю, что ОП хотел сделать с этим кодом, но когда я включаю объявление для strdup, код работает, т.е. тест печатается 5 раз. Не могли бы вы объяснить это для меня. спасибо! - person 0decimal0; 02.07.2013
comment
@PHIfounder C позволяет вам делать вещи, которые вызывают неопределенное поведение, например исходный код здесь. То, что происходит в таких случаях, может быть очень случайным, это может быть катастрофический сбой или может показаться, что все работает нормально, по крайней мере, сегодня. - person nos; 02.07.2013
comment
@PHIfounder: вы можете запустить программу, скомпилированную из исходного кода OP, используя Valgrind (valgrind.org), и вы увидеть, что происходит не так за кулисами, даже если программа может не рухнуть. - person alk; 02.07.2013
comment
Работает ли Valgrind на Windows? Я использую гдб. - person 0decimal0; 02.07.2013
comment
@PHIfounder: Вы используете MinGW или Cygwin? Для MinGW это может помочь: sourceforge.net/p/valgrind4win/wiki/DevelopmentEnvironment - person alk; 02.07.2013
comment
MinGW, большое спасибо за ссылку. Голос за вас. - person 0decimal0; 02.07.2013

Следуя своему стилю, меняй

path = (char **) realloc(path, sizeof(char *));

to

path = realloc(path, (i+2) * sizeof(char *));

Это выделяет для цикла следующий. Если вы хотите перераспределить ресурсы для каждого цикла, имеет смысл выделить заранее, как в следующем примере:

path = realloc(path, (i+1) * sizeof(char *));
path[i] = strdup("TEST");

OR

Как говорят другие, выделите всю свою память заранее с помощью path = malloc(5 * sizeof(char *)). Отбрасывание приведения (char **) является предпочтительной практикой кодирования.

OR

Выделите всю свою память заранее с помощью

path = calloc(5, sizeof(char *));

Это имеет 2 преимущества calloc(num,size) над malloc(size). Память заполнена нулями. Если умножение num * size переполнится (очевидно, не в этом случае), calloc() все равно правильно обработает это.


Другая практика кодирования подумала: использование ptr = malloc(sizeof(*ptr)) имеет хорошее преимущество перед ptr = malloc(sizeof(char *)). Если тип *ptr изменится (хотя здесь это маловероятно), в различных вызовах malloc() не потребуется никаких изменений в коде. (Также относится к realloc() и calloc().)

person chux - Reinstate Monica    schedule 02.07.2013
comment
Это, вероятно, то, что должен был означать код, но это заслуживает некоторого объяснения. - person Jan Hudec; 02.07.2013
comment
Требуется i+2, после доступа к path[i] path уже должно указывать как минимум на i+1 элементов, иначе у вас есть UB. - person Daniel Fischer; 02.07.2013
comment
@Daniel Fischer Хороший улов на +2. - person chux - Reinstate Monica; 02.07.2013
comment
@DanielFischer согласился с вами после запуска кода, но не может понять, зачем нужен i+2, и не может понять, что такое UB. - person mSatyam; 02.07.2013
comment
Во второй раз в цикле OP for происходит назначение path[1] = strdup("TEST");. К этому времени path должно быть 2 указателя. Таким образом, функция realloc() предыдущего цикла, когда i была равна 0, должна обеспечивать 2. В последний и 5-й раз в цикле, когда i = 4, функция realloc() будет равна 6. path[6] никогда не будет использовал. Тааак, делайте reallloc() до присваивания, а не после. - person chux - Reinstate Monica; 02.07.2013
comment
Еще одна вещь, которую мне нужно спросить, предположим, если я выделил 4 байта памяти с помощью malloc, а затем позже я сделал перераспределение, передав базовый адрес памяти, выделенной ранее с помощью malloc, и в realloc я указал выделение 8 байтов памяти. Итак, теперь всего у меня будет 12 байт памяти или 8 байт памяти. - person mSatyam; 05.07.2013
comment
@msatyam 8 байт. Первые 4 освобождаются при вызове realloc (). - person chux - Reinstate Monica; 08.07.2013