C — Malloc и memcpy (управление памятью)

Я немного новичок в C, и мне трудно понять, как работает память, особенно встроенные функции, такие как memcpy.

Вот struct, которым я пользуюсь

 struct data_t {
    int datasize;   
    void *data; 
 };

И вот вспомогательная функция, с которой я ее использую:

struct data_t *data_create(int size)
{
   struct data_t *dt=malloc(sizeof(struct data_t)+size);
   dt->datasize=size;   
   dt->data="1234567890a";
   return dt;
}

Теперь в функции main у меня нет проблем с этим:

struct data_t *data = data_create(1024);
data->data="123456a";//just an example

Но это вызывает Seg Fault:

memcpy(data->data,"123456a",strlen("1234567890a")+1);

Мой вопрос: почему? И как мне этого избежать? Пожалуйста, имейте в виду, что я новичок в C, поэтому то, как C работает с памятью, для меня немного новое.

Спасибо.

Редактировать: Это работает! Большое тебе спасибо. Полностью пропустил указатель данных. Теперь все работает нормально по valgrind.


person PTdude    schedule 08.10.2011    source источник


Ответы (4)


memcpy(data->data,"123456a",strlen("1234567890a")+1);

терпит неудачу, потому что data->data тип void * указывает на какой-то мусор/недопустимый адрес, который не выделен. data имеет адрес строкового литерала, который хранится в разделе только для чтения (как в .rodata исполняемого файла и загружается в память, которая недоступна для записи. Также, если вы не назначили такой строковый адрес в переменную указателя, тогда он будет содержать какое-то недопустимое/мусорное значение адреса, которое не выделено или не инициализировано каким-либо допустимым разрешенным местоположением.Поэтому сначала выделите буфер.

data->data = malloc (sizeof (char) * size);

malloc вернет адрес первого местоположения блока адресов размером не менее size * sizeof (char) байт. Теперь вы можете скопировать size байтов в эту область памяти, на которую указывает data->data.

Не забудьте освободить выделенный блок памяти, когда вы закончите работу с этим блоком памяти с помощью вызова free (addr).


Я вижу, вы пытались выделить буфер data очень странным образом (?):

struct data_t *dt=malloc(sizeof(struct data_t)+size);

для которых дополнительные выделенные size байтов вместе с struct data_t. Но как бы то ни было, компонент data все равно указывает на какое-то место, которое нельзя изменить. Пожалуйста, используйте:

struct data_t *dt = malloc(sizeof(struct data_t));
dt->data = malloc (sizeof (char) * size);
memcpy (data->data, "whatever", sizeof ("whatever")+1);
return dt;

чтобы бесплатно сначала сделать:

free (dt->data);

потом

free (dt);
person phoxis    schedule 08.10.2011
comment
Пространство было выделено при первоначальном распределении; указатель просто не был установлен так, чтобы указывать на выделенное пространство. - person Jonathan Leffler; 08.10.2011
comment
@JonathanLeffler: да, спрашивающий выделил дополнительное пространство при первоначальном выделении, он должен был инициализироваться до точки. Я все же предпочту явный вызов, если нет особой необходимости выделять все сразу. - person phoxis; 08.10.2011
comment
это выдаст ошибку: неверное преобразование из «void*» в «data_t*» [-fpermissive], чтобы решить эту проблему, вы должны явно указать указатель, возвращаемый malloc. - person VasaraBharat; 17.11.2017
comment
@VasaraBharat: Вы правы, но это на C++. Вопрос был помечен как C, для которого явное преобразование не требуется. См. C99, раздел 6.3.2.3, абзац 1. В то время как в C++ это явно не упоминается (или я не знаю). - person phoxis; 20.11.2017

Ваша первая ошибка заключается в следующем:

struct data_t *dt=malloc(sizeof(struct data_t)+size);

Это создаст кусок памяти размером struct data_t + size. Я думаю, вы ожидали, что ваше поле данных внутри data_t может использовать эту память, но не может, потому что данные не содержат адрес этой памяти.

Ваша вторая ошибка заключалась в том, чтобы предположить, что вы копируете значение следующей строки в «данные»:

data->data="123456a";

На самом деле здесь произошло то, что в памяти есть строка «123456a», которая существует на протяжении всей жизни вашей программы. Когда вы назначаете «123456a» для данных-> данные, на самом деле происходит то, что вы берете адрес этой строки «123456a» и помещаете его в данные-> данные, вы копируете не значение («123456a»), а местоположение или адрес (0x23822...) "123456a".

Ваша последняя ошибка заключалась в следующем:

memcpy(data->data,"123456a",strlen("1234567890a")+1);

Вы попытались скопировать значение "123456a" в память, на которую указывает data. На что указывают данные? Он указывает на область памяти, доступную только для чтения, содержащую вашу ранее назначенную строку «123456a». Другими словами, вы сказали своей программе писать по адресу «123456a».

Вот программа, которая будет делать то, что вы ожидаете:

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

typedef struct {
    size_t datasize;   
    char *data; 
} data_t;

data_t *data_create(size_t size)
{
   data_t *dt;

   dt = malloc(sizeof(data_t));
   assert(dt != NULL);
   dt->data = malloc(size);
   assert(dt->data != NULL);
   dt->datasize = size;

   /* need to decide what to do when this happens */
   assert((strlen("1234567890a") + 1) < size);
   strcpy(dt->data, "1234567890a");

   return dt;
}

void data_destroy(data_t *dt)
{
    free(dt->data);
    free(dt);
}

int main(void)
{
    data_t *data = data_create(1024);
    /* data->data="123456a"; DONT DO THIS YOU WILL CAUSE A MEMORY LEAK */

    assert(data->datasize >= (strlen("123456a")+1));
    memcpy(data->data, "123456a", strlen("123456a")+1);

    printf("%s\n", data->data);

    data_destroy(data);

    return EXIT_SUCCESS;
}
person megazord    schedule 08.10.2011

Обратите внимание, что void *data — это указатель, в data_create вы не выделили для него место, вы просто указываете на строковую константу "1234567890a", которая доступна только для чтения.

В main вы создаете другую строковую константу "123456a", затем делаете void *data указателем на строковую константу, которая доступна только для чтения.

Поэтому, когда вы вызываете memcpy для записи в адрес памяти, который не доступен для записи (или не инициализирован), вы получаете ошибку.

person lostyzd    schedule 08.10.2011

данные->данные - это указатель. И этот указатель указывает в никуда. Прежде чем делать memcpy, вы должны выделить пространство и сделать так, чтобы data->data указывало на это пространство.

data->data = malloc(strlen("1234567890a")+1);

и тогда memcpy не завершится ошибкой, пока data->data != NULL

делает

data->data = "123"

это нормально, потому что «123» выделяется во время компиляции, поэтому данные-> данные указывают на начало строки «123», но вызов memcpy(data->data,"123",4) завершится ошибкой, потому что указатель на данные-данные не инициализирован и указывает на какое-то случайное место в памяти, которое можно даже не читать.

person Luka Rahne    schedule 08.10.2011
comment
memcpy по-прежнему недействителен, если пытается скопировать из источника больше байтов, чем есть у источника, что и имеет место здесь. - person Mat; 08.10.2011