gcc memcpy真的是内存的副本吗?

wnvonmuf  于 2022-12-13  发布在  其他
关注(0)|答案(1)|浏览(143)

我正在用C语言试验内存处理。
给出以下代码

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

typedef unsigned char BYTE;

typedef struct Data {
    int valid;
    double value;
} Data;

typedef struct Message {
    int id;
    int size;
    int nr;
    Data *data;
} Message;

int main() {
    int sz = 5;
    int id = 1;
    int i;

    Message msg;
    msg.id = id;
    msg.size = 0;
    msg.nr = sz;
    msg.data = malloc(sizeof(Data) * msg.nr);
    for (i = 0; i < msg.nr; i++) {
        msg.data[i].valid = 1;
        msg.data[i].value = (double)i;
    }

    printf("Input data\nid: %d\nsize: %d\nnr: %d\n",
           msg.id, msg.size, msg.nr);
    
    for (i = 0; i < sz; i++)
        printf("msg.data[%d].valid: %d\nmsg.data[%d].value: %lf\n",
               i, msg.data[i].valid, i, msg.data[i].value);

    int bufferSize = sizeof(msg) + (sizeof(Data) * msg.nr);
    msg.size = bufferSize;
    printf("bufferSize: %d\n", bufferSize);

    BYTE *buffer = malloc(sizeof(BYTE) * bufferSize);
    memcpy(buffer, &msg, bufferSize);

    if (msg.data != NULL)
        free(msg.data);
     
    // test
    Message *p = (Message *)buffer;
    Message rcv;
    rcv.id = 0;
    rcv.size = 0;
    rcv.nr = 0;
    rcv.data = malloc(sizeof(Data) * p->nr);

    memcpy(&rcv, buffer, p->size);
    
    printf("Output data\nid: %d\nsize: %d\nnr: %d\n",
           rcv.id, rcv.size, rcv.nr);
    
    for (i = 0; i < sz; i++)
        printf("rcv.data[%d].valid: %d\nrcv.data[%d].value: %lf\n",
               i, rcv.data[i].valid, i, rcv.data[i].value);

    if (rcv.data != NULL)
        free(rcv.data);

    if (buffer != NULL)
        free(buffer);
}

我在代码*** stack smashing detected ***: terminated执行结束时得到以下错误
更进一步,我发现msg.datarcv.data指向相同的内存地址
memory location
当我释放rcv.data的时候,基本上我释放了一个内存位置,这个位置之前已经被释放过了。
我读到memcpy应该创建一个副本,但我有不同的经历。我不明白为什么会发生这种情况。
我使用gcc作为编译器,并且我尝试在不同的机器上运行代码,但是我总是得到相同的结果。为什么会出现这种行为?

oknwwptz

oknwwptz1#

为什么代码包含未定义的行为

memcpy的第一次调用从msg的地址开始读取bufferSize字节,其中bufferSize * 大于 * msg本身的大小。读取超出对象大小的内容会导致未定义的行为。

int bufferSize = sizeof(msg) + (sizeof(Data) * msg.nr);
// This reads beyond the size of `msg`
memcpy(buffer, &msg, bufferSize);

memcpy函数逐字节复制数据,包括MessageData* data字段,这就是为什么msg.datarcv.data的地址相同。memcpy不会试图创建data指针所指向数组的副本。复制超出msg末尾的字节也没有帮助。因为msg(在堆栈上)不会与msg.data(在堆上)指向的数组相邻。
memcpy entry on cppreference

任意长度数组的解决方案

若要修正这个问题,请分别呼叫memcpy两次,以序列化MessageData的数组。

int bufferSize = sizeof(Message) + sizeof(Data) * msg.nr;
BYTE* buffer = malloc(sizeof(BYTE) * bufferSize);
memcpy(buffer, &msg, sizeof(Message));
memcpy(buffer + sizeof(Message), msg.data, sizeof(Data) * msg.nr);

类似地,在接收端,调用memcpy两次,以获取MessageData的数组。

Message rcv;
memcpy(&rcv, buffer, sizeof(Message));
rcv.data = malloc(sizeof(Data) * rcv.nr);
memcpy(rcv.data, buffer + sizeof(Message), sizeof(Data) * rcv.nr);

Try it interactively on godbolt

针对大小受限阵列的解决方案

如果data数组具有固定的最大大小NUM_DATA_MAX是可以接受的,那么可以通过1次而不是2次调用memcpy来完成(反)序列化。为此,定义Message以包含数组data,而不是指向数组的指针。

#define NUM_DATA_MAX 10

typedef struct Data {
    int valid;
    double value;
} Data;

typedef struct Message {
    int id;
    int size;
    int nr;
    Data data[NUM_DATA_MAX];
} Message;

在发送端

int bufferSize = sizeof(Message);
BYTE* buffer = malloc(bufferSize);
memcpy(buffer, &msg, sizeof(Message));

在接收端

Message rcv;
memcpy(&rcv, buffer, sizeof(Message));

相关问题