了解 malloc() 在此上下文中的使用方式



我不确定按照社区标准这是否是一个好问题(请告诉我这个问题是否有更好的方法或位置)。

我正在努力理解我在尝试学习C++时遇到的一段代码。代码如下:

MessageHdr *msg;
size_t msgsize = sizeof(MessageHdr) + sizeof(joinaddr->addr) + sizeof(long) + 1;
msg = (MessageHdr *) malloc(msgsize * sizeof(char));
// create JOINREQ message: format of data is {struct Address myaddr}
msg->msgType = JOINREQ;
memcpy((char *)(msg+1), &memberNode->addr.addr, sizeof(memberNode->addr.addr));
memcpy((char *)(msg+1) + 1 + sizeof(memberNode->addr.addr), &memberNode->heartbeat, sizeof(long));
emulNet->ENsend(&memberNode->addr, joinaddr, (char *)msg, msgsize);
  1. 在第 3 行中投射到MessageHdr *有什么意义?
  2. 我觉得我们正在建立一个char[].我们只是用MessageHdr*来指代(指向)它,但我不确定为什么?char*不是更好的选择吗?

接收代码如下(缩短):

int EmulNet::ENsend(Address *myaddr, Address *toaddr, char *data, int size) {
en_msg *em;
...
em = (en_msg *)malloc(sizeof(en_msg) + size);
em->size = size;
memcpy(&(em->from.addr), &(myaddr->addr), sizeof(em->from.addr));
memcpy(&(em->to.addr), &(toaddr->addr), sizeof(em->from.addr));
memcpy(em + 1, data, size);
...

在这一点上,我感到非常困惑 - 对不起这个模糊的问题。这是惯用语C++吗?我觉得这本可以用更干净的方式完成,而不是传递char[]并通过随机结构类型的指针引用它。

我想我最终想问的是,虽然我有点理解代码,但感觉非常不自然。这是一种有效/常见的做事方法吗?

编辑


MessageHdr 是一个结构体,如下所示:

typedef struct MessageHdr {
enum MsgTypes msgType;
}MessageHdr;

joinaddr 是一个类 intances:

class Address {
public:
char addr[6];
Address() {}
// Copy constructor
Address(const Address &anotherAddress);
// Overloaded = operator
Address& operator =(const Address &anotherAddress);
bool operator ==(const Address &anotherAddress);
Address(string address) {
size_t pos = address.find(":");
int id = stoi(address.substr(0, pos));
short port = (short)stoi(address.substr(pos + 1, address.size()-pos-1));
memcpy(&addr[0], &id, sizeof(int));
memcpy(&addr[4], &port, sizeof(short));
}
string getAddress() {
int id = 0;
short port;
memcpy(&id, &addr[0], sizeof(int));
memcpy(&port, &addr[4], sizeof(short));
return to_string(id) + ":" + to_string(port);
}
void init() {
memset(&addr, 0, sizeof(addr));
}
};

代码真的很混乱。我将尝试解释我理解的第一部分。目的肯定是创建一个(结构化的)字符缓冲区来发送它。这可能是在c年创建的,或者由c程序员创建的。

MessageHdr *msg;

这将计算生成的发送缓冲区的大小

size_t msgsize = sizeof(MessageHdr) + sizeof(joinaddr->addr) + sizeof(long) + 1;

分配缓冲区。需要强制转换以允许 c++ 编译它,否则它将出错。

msg = (MessageHdr *) malloc(msgsize * sizeof(char));

这用于在缓冲区中设置字段。由于它是 MessageHdr 类型,因此它将值写入缓冲区的正确位置

// create JOINREQ message: format of data is {struct Address myaddr}
msg->msgType = JOINREQ;

这些命令使用带有 (MessageHdr) 类型的指针算术在缓冲区中写入 MessagHdr 本身之外的数据。msg + 1将跳过 char* 缓冲区中 MessageHdf 的大小。

memcpy((char *)(msg+1), &memberNode->addr.addr, sizeof(memberNode->addr.addr));
memcpy((char *)(msg+1) + 1 + sizeof(memberNode->addr.addr), &memberNode->heartbeat, sizeof(long));

这将通过首先将缓冲区作为一组简单的字节强制转换为char*来发送缓冲区。

emulNet->ENsend(&memberNode->addr, joinaddr, (char *)msg, msgsize);

接收代码似乎向数据添加了地址标头以进一步发送(类似于tcp-ip)

这将分配另一个缓冲区,其大小为en_msg标头的大小 + 数据的大小。

em = (en_msg *)malloc(sizeof(en_msg) + size);
em->size = size; // keeps data size in the en_msg struct

填写缓冲区en_msg部分中的地址字段

memcpy(&(em->from.addr), &(myaddr->addr), sizeof(em->from.addr));
memcpy(&(em->to.addr), &(toaddr->addr), sizeof(em->from.addr));

这会复制缓冲区中的数据,从 en_msg 标头之外开始

memcpy(em + 1, data, size);

.

你没有提供有关MessageHdrAddressen_msg的详细信息。但其中一些可能是struct,而不是简单的类型。

对于第一个问题:malloc返回一个void*,但在第3行中,分配的内存被分配给类型为MessageHdr*的指针,因此malloc的返回值需要转换为正确的类型。

以这种方式使用structs 是很常见的,因为它提供了一种简单的方法来处理不同类型的多个变量,这些变量应该属于一起(例如Address可以是端口具有一些int变量和主机名char[]struct)。

例:

struct Data
{
int something;
char somethingElse[10];
};
void* foo = malloc(100);                  // allocate 100 bytes of memory
void* bar = malloc(sizeof(struct Data));  // allocate a piece of memory with the size of struct Data
Data* data = (Data*)bar;                  // use the piece of memory
data->something = 10;                     // as Data struct
strcpy(data->something, "bla");

请注意,当然,您可以以任何您想要的方式使用分配的内存。 例如,在上面,您只需执行memcpy(foo, someSource, 100)即可将 100 字节复制到分配的缓冲区中。

在C++中,您将使用new运算符,其工作原理略有不同。除了为给定类分配内存外,它还将调用类构造函数。

对于问题 2: 同样,您没有提供有关MessageHdr的详细信息.如果它不是struct,而只是一个类型定义,例如。char[10],你是对的,你可以用字符[10]代替。
但是,想象一下,在整个程序或库中,您需要一遍又一遍地处理"MessageHdr"(Message-Header?),并且每次都是长度为10的char数组。使用 typedef 可以获得以下好处:

  1. 具有命名类型,其他人可能会立即识别并理解它的作用。
  2. 可以轻松更改 char 数组的大小,以防以后需要更改。

此代码C++代码无效。指针被强制转换为 '(MessageHDR*),以便编译器不会抱怨此代码:

msg->msgtype=JOINREQ

但是,如果 MessageHDR 具有空的初始化(见下文),则此代码是未定义的行为:这是无效C++代码(访问对象成员超出其生命周期)。 n.m. in is comment 建议你读一本书,这是最好的解决方案,如果你读代码,最好读写好的C++代码: stdlibc++, libc++ for exemple.


根据 c++ 标准 [basic.life]/1

对象或引用的

生存期是对象或引用的运行时属性。一个对象被称为具有 非空初始化,如果它是类或聚合类型,并且它或其子对象之一由 构造函数,而不是普通的默认构造函数。[ 注意:通过简单的复制/移动构造函数初始化 是非空初始化。— 尾注 ] 类型 T 对象的生存期从以下时间开始: (1.1) — 获得具有 T 型正确对齐和大小的存储,并且 (1.2) — 如果对象具有非空初始化,则其初始化完成

因此,如果MessageHDR有一个非空的初始化(这是使用 C++ 的重点),那么 [basic.life]/6

在对象的生存期开始之前,但在对象将占用的存储之后 已分配,或者在对象的生存期结束后,在对象占用的存储之前 重用或释放,表示对象将所在的存储位置地址的任何指针,或 可以以有限的方式使用。关于正在建造或销毁的物体,见15.7。 否则,此类指针引用分配的存储 (6.7.4.2),并且使用该指针就像指针是 类型void*是明确定义的。允许通过此类指针进行间接寻址,但生成的左值只能 以有限的方式使用,如下所述。如果出现以下情况,程序具有未定义的行为:[...]

(6.2) — 指针用于访问非静态数据成员或调用 对象,o

最新更新