Reputation: 1013
I'm not sure if this is a good question by community standards (let me know if there is a better way or place for this question please).
I'm working on understanding a piece of code which I've come across while trying to learn C++. Code is as follows:
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);
MessageHdr *
in line 3? char[]
. We're just using MessageHdr*
to refer (to point) to it but I am not sure why? Wouldn't a char*
be a better choice?Receiving code is as follows (shortened):
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);
...
I'm beyond confused at this point - sorry for the vague question. Is this idiomatic C++? I feel as if this could have been done in much cleaner ways instead of passing around a char[]
and referencing it via pointers of random struct types.
I guess what I'm ultimately trying to ask is, while I kind of understand the code, it feels very unnatural. Is this a valid/common approach of doing things?
EDIT
MessageHdr is a struct as follows:
typedef struct MessageHdr {
enum MsgTypes msgType;
}MessageHdr;
joinaddr is a class 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));
}
};
Upvotes: 0
Views: 225
Reputation: 18041
This code is invalid C++ code. The pointer is cast to `(MessageHDR*) in order that the compiler does not complain about this code:
msg->msgtype=JOINREQ
But this code is undefined behaviour if MessageHDR has vacuous initialization(see below): this is invalid C++ code (access object member out of its life-time period). n.m. in is comment propose you to read a book, this is the best solution, and if you read code, it would be better to read well written C++ code: stdlibc++, libc++ for exemple.
According to the c++ standard [basic.life]/1
The lifetime of an object or reference is a runtime property of the object or reference. An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a constructor other than a trivial default constructor. [ Note: Initialization by a trivial copy/move constructor is non-vacuous initialization. — end note ] The lifetime of an object of type T begins when: (1.1) — storage with the proper alignment and size for type T is obtained, and (1.2) — if the object has non-vacuous initialization, its initialization is complete
So if MessageHDR
has a non-vacuous initialization (which is the point of using C++) then [basic.life]/6
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 15.7. Otherwise, such a pointer refers to allocated storage (6.7.4.2), and using the pointer as if the pointer were of type void*, is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:[...]
(6.2) — the pointer is used to access a non-static data member or call a non-static member function of the object, o
Upvotes: -1
Reputation: 12344
The code is really confusing. I'll try to explain the first part as I understand it. The intention definitely was to create a (structured) char buffer to send it over. This was probably initially created in c
, or by a c
programmer.
MessageHdr *msg;
this calculates size of the resulting send buffer
size_t msgsize = sizeof(MessageHdr) + sizeof(joinaddr->addr) + sizeof(long) + 1;
allocates the buffer. The cast is needed to allow c++ to compile it, otherwise it will error-out.
msg = (MessageHdr *) malloc(msgsize * sizeof(char));
This is used to set up a field in the buffer. Since it is of Type MessageHdr, it writes the value in the correct place of the buffer
// create JOINREQ message: format of data is {struct Address myaddr}
msg->msgType = JOINREQ;
These commands use pointer arithmetic with (MessageHdr) type to write data in the buffer beyond the MessagHdr itself. msg + 1
will skip the size of the MessageHdf in the char* buffer.
memcpy((char *)(msg+1), &memberNode->addr.addr, sizeof(memberNode->addr.addr));
memcpy((char *)(msg+1) + 1 + sizeof(memberNode->addr.addr), &memberNode->heartbeat, sizeof(long));
this will send the buffer by casting it to char*
first, as a simple set of bytes.
emulNet->ENsend(&memberNode->addr, joinaddr, (char *)msg, msgsize);
The receiving code seems to add yet address header to the data to send it further (tcp-ip like)
This allocates another buffer with the size of the en_msg
header + size of the data.
em = (en_msg *)malloc(sizeof(en_msg) + size);
em->size = size; // keeps data size in the en_msg struct
fills out address fields in the en_msg part of the buffer
memcpy(&(em->from.addr), &(myaddr->addr), sizeof(em->from.addr));
memcpy(&(em->to.addr), &(toaddr->addr), sizeof(em->from.addr));
and this copies the data in the buffer starting just beyond the en_msg header
memcpy(em + 1, data, size);
.
Upvotes: 2
Reputation: 3206
You didnt give details about MessageHdr
, Address
and en_msg
. But some of them might be struct
s, and not simple types.
For the first question:
malloc
returns a void*
, but in line 3 the allocated memory is assigned to a pointer of type MessageHdr*
, thus the return value of malloc
needs to be casted to the correct type.
It is quite common to use struct
s in this way, as it provides a simple way of dealing with lets say multiple variables of different type, which shall belong together (e. g. Address
could be a struct
with some int
variable for the port, and a char[]
for the hostname).
Example:
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");
Note that of course you could use the piece of allocated memory in any way you want. E. g. in the above you could just do memcpy(foo, someSource, 100)
to copy 100 bytes into the allocated buffer.
In C++ you would use the new
operator, which works slightly different. In addition to allocating memory for a given class, it would also call the classes constructor.
For question 2:
Again you didn't give details about MessageHdr
. In case it is not a struct
, but only a typedef to e. g. char[10]
, you are right in that you could just use char[10] instead.
However, imagine throughout your program or library you need to deal with "MessageHdr" (Message-Header?) over and over again, and every time it is a char array with the length 10. Using a typedef you gain the benefit of:
Upvotes: 0