我不确定按照社区标准这是否是一个好问题(请告诉我这个问题是否有更好的方法或位置)。
我正在努力理解我在尝试学习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);
- 在第 3 行中投射到
MessageHdr *
有什么意义? - 我觉得我们正在建立一个
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);
.
你没有提供有关MessageHdr
、Address
和en_msg
的详细信息。但其中一些可能是struct
,而不是简单的类型。
对于第一个问题:malloc
返回一个void*
,但在第3行中,分配的内存被分配给类型为MessageHdr*
的指针,因此malloc
的返回值需要转换为正确的类型。
以这种方式使用struct
s 是很常见的,因为它提供了一种简单的方法来处理不同类型的多个变量,这些变量应该属于一起(例如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 可以获得以下好处:
- 具有命名类型,其他人可能会立即识别并理解它的作用。
- 可以轻松更改 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