0

I am writing this C++ code that receives packages from remote server. Since the size of the package is not known in advance, the code first receives 8 bytes which tells the size of the whole package. Then it allocates a large enough buffer and receive the whole package into the buffer.

My question is with the casting from const char* to uint64_t* part: is it safe to cast the pointer and then read the content as uint64_t? What if the buf is not aligned to 8 bytes?

const char* buf;
RecvBytes(buf, sizeof(uint64_t));    // the first 8 bytes should tell us the size of the whole package
uint64_t pkg_size = *(uint64_t*)buf;  // is this safe??
const char* pkg = new char[pkg_size];
RecvBytes(pkg, pkg_size);
Stargateur
  • 24,473
  • 8
  • 65
  • 91
hjk41
  • 239
  • 2
  • 10
  • 2
    I assume that `buf` actually points to some valid allocated memory? – Some programmer dude Jan 02 '18 at 07:11
  • 5
    Why don't you just pass a pointer to a `uint64_t` variable, then it will be aligned properly. – Barmar Jan 02 '18 at 07:11
  • 1
    Why `const char *` ??? – Stargateur Jan 02 '18 at 07:13
  • You have not shown how and where `buf` is initialized. It affects greatly on result. If `buf` points to plain `char` array, you have worse problems than alignment issues. – user694733 Jan 02 '18 at 07:15
  • 1
    If it's `const` you shouldn't be passing it to a function that modifies what it points to. – Barmar Jan 02 '18 at 07:15
  • If I understand the question, you are concerned with whether what `RecvBytes(buf, sizeof(uint64_t));` puts in `buf` can be interpreted as a `uint64_t`. If that is your question, then it will depend on whether what is sent is sent in the correct byte-order (little/big endian) and whether `RecvBytes` messes that up in any way. Presuming there is a proper conversion from host to network before the bytes are sent, then you may need a network to host conversion on your end. – David C. Rankin Jan 02 '18 at 07:16
  • 1
    Regarding the comment from @Barmar, you say that both `buf` and `pkg` are pointing to memory that is constant and can't be modified. Attempting to write to "constant" memory leads to *undefined behavior*. – Some programmer dude Jan 02 '18 at 07:21

4 Answers4

2

Well, in general, this should not be okay, but in one case, it may work, depending on the type of actual argument passed from the sender.

In other words, if the sender is sending a pointer to uint64_t type, only in that case, the code is acceptable.

Quoting C11, chapter §6.3.2.3

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned68) for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
0

In general, it is only safe to cast a (const) char * to a type, if the pointer was obtained from that type and then casted to char *.

So here the bullet proof way is:

uint64_t pkg_size;
RecvBytes((const char *) &pkg_size, sizeof(uint64_t));
const char* pkg = new char[pkg_size];
RecvBytes(pkg, pkg_size);
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
0

I am writing this C++ code that receives packages from remote server. Since the size of the package is not known in advance, the code first receives 8 bytes which tells the size of the whole package. Then it allocates a large enough buffer and receive the whole package into the buffer.

I think in the context of package exchange you should never allocate dynamically something or at least not without a max size. (And loop if necessary to compute all data)

I will in your case do something like this:

union {
  uint64_t size;
  char bytes[sizeof size];
} tmp;
RecvBytes(tmp.bytes, sizeof tmp.bytes);
if (tmp.size > SIZE_MAX) {
  // bad
}
size_t size = static_cast<size_t>(tmp.size); // or use tmp.size

char data[MAX]; // or use something more object
for (size_t i = 0; i < size; i += MAX) {
  RecvBytes(data, min(size - i, MAX));
  // compute data
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
0

My question is with the casting from const char* to uint64_t* part: is it safe to cast the pointer and then read the content as uint64_t?

In general, it is not safe and is undefined behavior, so you should be scared. For example, your code could crash on a PowerPC machine, where pointers to uint64_t should be aligned to 8 bytes (otherwise, a SIGBUS or SIGSEGV could occur when dereferencing a misaligned pointer).

What if the buf is not aligned to 8 bytes?

On x86 processors, you might use them: dereferencing a misaligned pointer is permitted, but is inefficient (so you should avoid doing that).

A dynamically allocated pointer (obtained by malloc or ::operator new) is guaranteed to be well enough aligned, so the result of new char[x] can be safely cast to a uint64_t*

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547