Can pointers be use to extract a uint32_t from a buffer?

From Examples > ESP8266WiFi > NTPClient, time requests come back via UDP and wind up in:

byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

the time stamp is in packetBuffer[40] to [43] and the example sketch extracts it like this:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;

I’ve started trying to learn about pointers, and think that it should be possible to point some code at packetBuffer[40] and then have it load into an unsigned long variable, (or uint32_t ? is there a difference?).

I though this would work:

uint32_t* longptr;
longptr = &packetBuffer[40];

but I got ==> error: cannot convert ‘char*’ to ‘uint32_t* {aka long unsigned int*}’ in assignment

I spent several hours trying everything I could think of (until at the end, just guesses at random combinations of * & packetBuffer[40], longptr… etc). As far as I understand, this should be possible, but then again I’m a total novice - if it were this simple, would not they have done that in the example? But that’s not really proof of anything, so my question has made its way here. Where am I with respect to having a clue?

How about

uint32_t* longptr;
longptr = (uint32_t *) (packetBuffer + 40);

packetBuffer if of type 'char *' so you have to cast it to the type you want

longptr = (uint32_t *) (&packetBuffer[40]);

Beavis4ever:
would not they have done that in the example?

The example is the way it is to avoid endianness trouble.

@hzrnbgy and @blh64 Thanks for the help with pointers, that did what I was trying do. Unfortunately for me,
@Coding Badly the endianness did turn out to be the wrong kind for my situation. I'm guessing there is no easy way around this, so I'll just use the approach used by the example. I should have known that if my way is simpler then my way is simply wrong! :slight_smile:

Well, presuming you really are converting from on-wire endianness to target endianness then you should be using ntohl. Which is a safe assumption given the fact that "NTP" and "UDP" appear in your post.

I'm not really sure what "...converting from on-wire endianness to target endianness..." means ("on-wire" is the device I'm in front of and "target" is across the network?), but from the link you gave, ntohl seems to be just what I need. Funny thing is, in order to get it to work I wound up on this Arduino SE page which seemed to say that ntohl was unavailable, and to use

#define htonl(x) __builtin_bswap32((uint32_t) (x))

When I noticed that it was for htonl and not ntohl, I started to wonder what the difference was etc. But since I got it to work without any compiler warnings, and it seems to reverse the endianness like I need, I guess I can ignore the difference in my present situation.

Anyhow, thanks so much for your help!

You're not allowed to cast a pointer to char to a pointer to unsigned long or uint32_t. There are no uint32_t variables in the packetBuffer array, so you cannot point to them using a uint32_t pointer. The code posted by hzrnbgy and blh64 is incorrect.
By explicitly casting one pointer type to another, you're not solving the error, you're just suppressing it. The resulting code is still invalid and invokes undefined behavior.

There are two correct solutions: using memcpy or using bit shifts.
Bit shifts have the advantage that you don't have to worry about Endianness. The example sketch is using word, which uses bit shifts implicitly.

There's no need to involve pointers here, and you definitely shouldn't use invalid casts. It won't be faster, and it's not easier to read.

If you want to read more about the topic of converting an array of bytes to uint32_t and the problems that arise when casting pointers of one type to another, have a look at this thread: https://forum.arduino.cc/index.php?topic=720420.msg4842429#msg4842429

Pieter

It is perfectly ok to cast a pointer to a different type, IF you know the byte sequence (Endian-ness) is compatible. Which means you KNOW the order of the bytes, and you KNOW the Endian-ness of the processor. But, in this case, I suspect the OP has not taken these factors into account, and it simply trying things until he finds something that works. Not a good approach...

I’ve always thought of “invoking undefined behaviour” as meaning that once done, literally anything could go wrong in any part of any code that was compiled in the same go.

Perhaps that is extreme or extremely wrong. Nevertheless it has largely kept me from taking tempting shot cuts no matter they seem to work.

a7

RayLivingston:
It is perfectly ok to cast a pointer to a different type, IF you know the byte sequence (Endian-ness) is compatible.

No, it is not. Casting a pointer to a pointer of a different type (a type that is not similar or a character type) invokes undefined behavior. It is not allowed by the C++ standard, it breaks the strict aliasing rule. Please see the discussion I linked to, and the documentation for reinterpret_cast (the C-style explicit casts are converted to reinterpret_casts by the compiler in this case).

You might get lucky, or the compiler devs might cut you some slack, so it might work in many cases, but it is simply invalid C++ code (and invalid C code as well, for that matter). There is no reason to use invalid casts like that, there are valid alternatives like memcpy or using bit shifts.

alto777:
I’ve always thought of “invoking undefined behaviour” as meaning that once done, literally anything could go wrong in any part of any code that was compiled in the same go.

Yes, that’s exactly what it means, which is why you should avoid it at all costs. What’s even worse is that many programs with undefined behavior appear to work correctly, until one day they don’t and everything breaks.

The compiler is there to help you, simply adding explicit casts or asterisks and ampersands until it compiles is always a terrible idea.

@PieterP Thanks for the warning in your post, but I had already abandoned that approach! (OK, while that may technically be true, it is also a bit misleading. I had to abandon the use of uint32_t because my next step was to send it over the “Serial.bus” ( :)) and the docs (I think) say I can’t do that).

#4 <= posts that I read from thread that you linked to <= #13

For some strange reason, I really like reading people “arguing” about stuff that I don’t understand! :slight_smile: Actually, I was able to follow some of what was being said; I did learn a bit. The big takeaway for me is (still) the amount of material that I’m utterly unaware of!

Maybe just a quick last question from my current thread, in post #6:

#define htonl(x) __builtin_bswap32((uint32_t) (x))

this code seemed to give me the result I needed, but it was for htonl and not ntohl. Why would the two give the same result? They didn’t apply to my use? From the looks of it, I didn’t even need to use “htonl” since it was a #define, I could have just called my thing (function?) dork(x) ? I know this last “question” isn’t really well thought out, so I’m not asking for a real thorough answer, maybe just your general thoughts on my using this approach or anything you care to mention.

Thanks so much for your help!

May I suggest that you use a struct instead of a byte array ? Here is some code from an old project

Beavis4ever:
Maybe just a quick last question from my current thread, in post #6:

#define htonl(x) __builtin_bswap32((uint32_t) (x))

this code seemed to give me the result I needed, but it was for htonl and not ntohl. Why would the two give the same result? They didn't apply to my use? From the looks of it, I didn't even need to use "htonl" since it was a #define, I could have just called my thing (function?) dork(x) ? I know this last "question" isn't really well thought out, so I'm not asking for a real thorough answer, maybe just your general thoughts on my using this approach or anything you care to mention.

Network byte order is Big-Endian. If your platform is Big-Endian as well, both ntohl and htonl are no-ops.
If your platform is Little-Endian, both ntohl and htonl reverse the bytes of the given integer. They are inverse operations, i.e. htonl(ntohl(x)) == ntohl(htonl(x)) == x, and since the inverse of byte reversal is byte reversal, htonl and ntohl are identical.
That doesn't mean you can just use them interchangeably, though: The different names show different intent. Keep in mind that you're writing code not only for the compiler to read, but also for human programmers (including your future self), so showing intent in your code is important. Use htonl if you're going to send the result over the wire, and use ntohl if you read the argument from the wire.

There's no real reason to use a macro here, just make it a function:

uint32_t htonl(uint32_t x) { return __builtin_bswap32(x); }

guix:
May I suggest that you use a struct instead of a byte array ? Here is some code from an old project

//#define DEBUG_NTPstruct{ uint8_t mode : 3; // Only three bit - Pastebin.com

Structs can have padding in between members, you cannot use them reliably for serialization.
If you really want to use this technique, you'll have to resort to non-standard compiler extensions like GCC's __attribute__((packed)).

PieterP:
Structs can have padding in between members, you cannot use them reliably for serialization.
If you really want to use this technique, you'll have to resort to non-standard compiler extensions like GCC's __attribute__((packed)).

I don't know for arduino boards, but I never had a problem with that on esp8266 and esp32 boards

And, is

__attribute__((packed))

the same as

#pragma pack(push, 1)
...
#pragma pack(pop)

?

guix:
I don't know for arduino boards, but I never had a problem with that on esp8266 and esp32 boards

It all depends on the platform you're using. For example, on most 32- and 64-bit architectures, the following struct will probably include padding to get 4-byte alignment for m2:

struct S {
  uint8_t m1;
  // probably 3 bytes of padding here
  uint32_t m2;
};

guix:
And, is

__attribute__((packed))

the same as

#pragma pack(push, 1)

...
#pragma pack(pop)




?

The result will probably be the same in most cases. I don't know if they're exactly equivalent in all cases, you would have to consult the GCC documentation if you want to be sure.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.