我正在用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.data
和rcv.data
指向相同的内存地址
memory location
当我释放rcv.data
的时候,基本上我释放了一个内存位置,这个位置之前已经被释放过了。
我读到memcpy
应该创建一个副本,但我有不同的经历。我不明白为什么会发生这种情况。
我使用gcc作为编译器,并且我尝试在不同的机器上运行代码,但是我总是得到相同的结果。为什么会出现这种行为?
1条答案
按热度按时间oknwwptz1#
为什么代码包含未定义的行为
对
memcpy
的第一次调用从msg
的地址开始读取bufferSize
字节,其中bufferSize
* 大于 *msg
本身的大小。读取超出对象大小的内容会导致未定义的行为。memcpy
函数逐字节复制数据,包括Message
的Data* data
字段,这就是为什么msg.data
和rcv.data
的地址相同。memcpy
不会试图创建data
指针所指向数组的副本。复制超出msg
末尾的字节也没有帮助。因为msg
(在堆栈上)不会与msg.data
(在堆上)指向的数组相邻。memcpy
entry on cppreference任意长度数组的解决方案
若要修正这个问题,请分别呼叫
memcpy
两次,以序列化Message
和Data
的数组。类似地,在接收端,调用
memcpy
两次,以获取Message
和Data
的数组。Try it interactively on godbolt
针对大小受限阵列的解决方案
如果
data
数组具有固定的最大大小NUM_DATA_MAX
是可以接受的,那么可以通过1次而不是2次调用memcpy
来完成(反)序列化。为此,定义Message
以包含数组data
,而不是指向数组的指针。在发送端
在接收端