Author Topic: Is there a difference between "unsigned long" and "uint32_t" for 32-bits MCU?  (Read 7917 times)

0 Members and 1 Guest are viewing this topic.

Offline unscriptedTopic starter

  • Contributor
  • Posts: 11
  • Country: pr
I'm going through other people code, just studying it and found these lines a bit confusing:

uint8_t *destPtr;

destPtr = (uint8_t *)((unsigned long)(uint32_t)&modInfo.Data[1]);

Does this make sense in a 32-bit MCU? Aren't unsigned long and uint32_t both 32 bits in size? Is there another difference than can make the application behave differently if replacing unsigned long with uint32_t?
 

Offline Kalvin

  • Super Contributor
  • ***
  • Posts: 2145
  • Country: fi
  • Embedded SW/HW.
I'm going through other people code, just studying it and found these lines a bit confusing:

uint8_t *destPtr;

destPtr = (uint8_t *)((unsigned long)(uint32_t)&modInfo.Data[1]);

Does this make sense in a 32-bit MCU? Aren't unsigned long and uint32_t both 32 bits in size? Is there another difference than can make the application behave differently if replacing unsigned long with uint32_t?

I guess this might be a bit simpler solution without unnecessary trickery:

Code: [Select]
destPtr = (uint8_t *) &modInfo.Data[1];
In order to be 100% sure whether your assumption is correct, you can use a static assert in your code:

Code: [Select]
static_assert(sizeof(unsigned long) == 4, "Unsigned long not 4 bytes");
static_assert(sizeof(uint32_t) == 4, "uint32_t not 4 bytes");

 
The following users thanked this post: unscripted

Offline Sacodepatatas

  • Regular Contributor
  • *
  • Posts: 89
  • Country: es

Code: [Select]
destPtr = (uint8_t *) &modInfo.Data[1];

I see still some redundancy within that code because of type conversion of a pointer to a variable and then getting the address of such var for casting to a uint8_t pointer type. I guess that the compiler takes care of this redundancy, but i think the code gets more simple by doing like this:

Code: [Select]
destPtr = (uint8_t *) (modInfo.Data+1);
 
The following users thanked this post: unscripted

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8191
  • Country: fi
unsigned long is of architecture-specific size. You need to read the documentation to find out, or write a simple test program which prints the result of sizeof (unsigned long).

This is EXACTLY why uint32_t and friends were introduced in C99. Just use them when it matters.

If you have to interface with existing code you don't want to modify, and you still need to assume the size, add a static assert like Kalvin showed.
 
The following users thanked this post: unscripted

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3148
  • Country: ca
I'm going through other people code, just studying it and found these lines a bit confusing:

uint8_t *destPtr;

destPtr = (uint8_t *)((unsigned long)(uint32_t)&modInfo.Data[1]);

Does this make sense in a 32-bit MCU? Aren't unsigned long and uint32_t both 32 bits in size? Is there another difference than can make the application behave differently if replacing unsigned long with uint32_t?

No. This just shows that a person who wrote the code had no idea what he's doing. uint32_t and Co are used when you want a variable of the specified length (32-bit) regardless of anything. Casting pointers to uint32_t will work if the pointer fits into 32 bits, and will crash otherwise (for example on a 64-bit CPU). Doing this where there's absolutely no need for such casting is plain stupid.
 
The following users thanked this post: unscripted

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14562
  • Country: fr
I'm going through other people code, just studying it and found these lines a bit confusing:

uint8_t *destPtr;

destPtr = (uint8_t *)((unsigned long)(uint32_t)&modInfo.Data[1]);

Does this make sense in a 32-bit MCU? Aren't unsigned long and uint32_t both 32 bits in size? Is there another difference than can make the application behave differently if replacing unsigned long with uint32_t?

The above line of code shows, at best, a poor command of C.

- The double cast doesn't add any value here.
- Casting a pointer to an integer doesn't make any sense in this context. It's absolutely useless, and could be wrong if pointers don't fit in a (whatever is shorter on the given platform) uint32_t or unsigned long. The right C type to use for casting pointers to integers is uintptr_t. But again in this context, it's absolutely useless even if using the right type. You don't need to cast to anything, the '&' operator already gives you a pointer, that can be cast to another pointer type directly, so all you need is 'destPtr = (uint8_t *) &modInfo.Data[1];'.
(Whether this cast would then make sense is another matter, depends on what you are accessing.)


 
The following users thanked this post: newbrain, unscripted, tellurium

Offline wek

  • Frequent Contributor
  • **
  • Posts: 496
  • Country: sk
But again in this context, it's absolutely useless even if using the right type.
It can be used to strip qualifiers thus avoid warnings.

JW
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14562
  • Country: fr
But again in this context, it's absolutely useless even if using the right type.
It can be used to strip qualifiers thus avoid warnings.

JW

No. You use cast to pointers directly for that. No need to cast to integers and back.
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 496
  • Country: sk
>>> But again in this context, it's absolutely useless even if using the right type.
>> It can be used to strip qualifiers thus avoid warnings.
> No. You use cast to pointers directly for that. No need to cast to integers and back.

Not if you use gcc with -Wcast-qual.

That may sound strange, but I want to catch *inadvertently* stripped qualifiers, so the cast through integer indicates intention. Disabling the warning through pragmas or function attributes is not always practical.

JW
 

Online JPortici

  • Super Contributor
  • ***
  • Posts: 3469
  • Country: it
Code: [Select]
destPtr = (uint8_t *) (modInfo.Data+1);

why would you do that? pointer arithmetic always gives me headaches and leaves room for nasty bugs. if the aim is to get the address of element one in the array, use the address of element one in the array. That is the most clear for humans
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3148
  • Country: ca
Code: [Select]
destPtr = (uint8_t *) (modInfo.Data+1);

why would you do that? pointer arithmetic always gives me headaches and leaves room for nasty bugs. if the aim is to get the address of element one in the array, use the address of element one in the array. That is the most clear for humans

It's the same. The C-99 says:

Quote
The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2)))

hence &x[y] is the same as &*(x+y) same as (x+y)
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8191
  • Country: fi
It's technically the same, but always consider what meaning you convey to the human reader of the code.

I prefer arithmetic using + when for example summing a peripheral register memory address from multiple offsets (like AHB_BASE + PERIPHERAL_BASE + 42) and then cast that to the final type.

When accessing something that's either an array, or usually is originally an array but lost its array-ness by being passed to a function as a pointer, it's quite obvious to use []:

Code: [Select]
void func(uint8_t *buf, size_t len)
{
    buf[x] // looks better...
}

// ... because it's usually used like this:
uint8_t buf[123];
func(buf, sizeof buf);
 
The following users thanked this post: newbrain, JPortici

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3148
  • Country: ca
When accessing something that's either an array, or usually is originally an array but lost its array-ness by being passed to a function as a pointer, it's quite obvious to use []:

Why is that?

Consider, for example, this:

Code: [Select]
ptr++;
vs this:

Code: [Select]
ptr = &ptr[1];
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8191
  • Country: fi
Yeah, ptr++ surely looks understandable, corresponding to natural language "take the next element as our baseline".

But, if you want to access the 42th element, then ptr[42] sounds more natural than *(ptr+42).

Good style depends on case.
 

Online JPortici

  • Super Contributor
  • ***
  • Posts: 3469
  • Country: it
Code: [Select]
destPtr = (uint8_t *) (modInfo.Data+1);

why would you do that? pointer arithmetic always gives me headaches and leaves room for nasty bugs. if the aim is to get the address of element one in the array, use the address of element one in the array. That is the most clear for humans

It's the same. The C-99 says:

Quote
The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2)))

hence &x[y] is the same as &*(x+y) same as (x+y)

I know it's technically the same. That's not the point.
using the address of the nth array element is a far more readable way. And less error prone (did you mean to get the address of the nth entry? or of the nth byte after the entry?)
« Last Edit: December 23, 2022, 06:08:49 pm by JPortici »
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14562
  • Country: fr
>>> But again in this context, it's absolutely useless even if using the right type.
>> It can be used to strip qualifiers thus avoid warnings.
> No. You use cast to pointers directly for that. No need to cast to integers and back.

Not if you use gcc with -Wcast-qual.

That may sound strange, but I want to catch *inadvertently* stripped qualifiers, so the cast through integer indicates intention. Disabling the warning through pragmas or function attributes is not always practical.

I see. Never enabled this warning but, why not. Although your approach indeed circumvents the warning and "kinda" indicates intention, a cast to integer and back may not be very obvious to the reader either here, so dunno. Obviously your coding style choice and why not. If you do that, just use (u)intptr_t  though. That's the only portable way of doing it.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14562
  • Country: fr
Code: [Select]
destPtr = (uint8_t *) (modInfo.Data+1);

why would you do that? pointer arithmetic always gives me headaches and leaves room for nasty bugs. if the aim is to get the address of element one in the array, use the address of element one in the array. That is the most clear for humans

It's the same. The C-99 says:

Quote
The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2)))

hence &x[y] is the same as &*(x+y) same as (x+y)

I know it's technically the same. That's not the point.
using the address of the nth array element is a far more readable way. And less error prone (did you mean to get the address of the nth entry? or of the nth byte after the entry?)

It's really down to coding style, I have no strong feeling either way. I personally have zero problem with pointer arthimetic that I find pretty straightforward once you stop thinking about C as assembly and stop thinking about C pointers as low-level addresses.

But to each their style, both can be defended.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3148
  • Country: ca
I know it's technically the same. That's not the point.
using the address of the nth array element is a far more readable way. And less error prone (did you mean to get the address of the nth entry? or of the nth byte after the entry?)

You always get the n-th element, not n-th byte.

To get a pointer to the n-th byte you need to cast to a type which is 1-byte long,  say

Code: [Select]
  uint8_t *a;

  a = &((uint8_t*)x)[2];
  a = (uint8_t*)x+2;
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3148
  • Country: ca
Yeah, ptr++ surely looks understandable, corresponding to natural language "take the next element as our baseline".

But, if you want to access the 42th element, then ptr[42] sounds more natural than *(ptr+42).

Good style depends on case.

Sure, accessing elements is more natural with [], getting pointers is another story.

I usually think of other things when I type, so I can type &ptr[42] or (ptr+42). Doesn't matter much to me. Right now (ptr+42) is somewhat more appealing to me, but I am sure there were times in my life where I had different preferences, and I sure can feel different about this in the future.  Within the same program, I always try to use the same method, but I have no logical explanation for that.
 
The following users thanked this post: Siwastaja, newbrain

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 3733
  • Country: gb
  • Doing electronics since the 1960s...
Surely the answer to the question is a simple "no" - for all normal CPUs.

And similarly an "int" is same as "long" and same as int32_t.

Are there any exceptions?

I am a mere novice here...

I never use a "long", but I do use "int" for trivia like for loop variables which only run from 0 to 1000 :) Plus there is the silent internal promotion of all integers to "int" in C.

My 32F4 code will never be ported down to a Z80, and I will be long dead before it gets ported to a 64 bit CPU, but if it was, it should still run the same, except any "int" will take up 8 bytes instead of 4.
« Last Edit: December 27, 2022, 07:29:06 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Kalvin

  • Super Contributor
  • ***
  • Posts: 2145
  • Country: fi
  • Embedded SW/HW.
Surely the answer to the question is a simple "no" - for all normal CPUs.

And similarly an "int" is same as "long" and same as int32_t.

Are there any exceptions?

I am a mere novice here...

I never use a "long", but I do use "int" for trivia like for loop variables which only run from 0 to 1000 :)

My 32F4 code will never be ported down to a Z80, and I will be long dead before it gets ported to a 64 bit CPU, but if it was, it should still run the same, except any "int" will take up 8 bytes instead of 4.

Check this Wikipedia page: https://en.wikipedia.org/wiki/C_data_types

C data type int is at least 16-bits in size. Typically the size of the int data type is identical to the native data size of the target system. For example, in systems with 16-bit architecture the size of the int is 16 bits, and in 32-bit architectures int is 32 bits. In 8-bit systems, the size of the int will be 16 bits by definition.

C data type long is at least 32-bits in size.

C data type int32_t is exactly 32-bits in size by definition.

As you can see, the data sizes of int, long and int32_t will probably be the same only in 32-bit architectures.

As a rule of a thumb it is recommended to use int in loops and for temporary variables (unless you know better, or the numerical range of int is not sufficient), as this will typically produce the best performance and best code size. Using long in the loops and for temporary variables where the numerical range of int would suffice is just a bad habit, which may lead poor performance and larger code size.
 
The following users thanked this post: peter-h, JPortici

Offline BBBbbb

  • Supporter
  • ****
  • Posts: 289
  • Country: nl
Regarding OP's Q at the start - I'm pretty sure this was done to eliminate some linter warnings, possibly on a legacy code later converted to use stdint.
I've seen this type of messy casting only when people set their brains on autopilot and start cleaning linter report after they've done the code. Many don't try to understand those warnings and see them as a nuisance, thus such castings occur.
 
The following users thanked this post: JPortici

Offline mikerj

  • Super Contributor
  • ***
  • Posts: 3245
  • Country: gb
Are there any exceptions?

It's not unusual for an int to be 16 bits on small (usually 8 bit) micros, the Microchip PIC C compiler for the 12F/16F/18F parts (formerly HTSoft) and most 8051 compilers use this.  There are also some oddball things like a "short long" being 24 bits on the PIC compiler, pretty handy for saving space.
 
The following users thanked this post: Kalvin

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8191
  • Country: fi
As a rule of a thumb it is recommended to use int in loops and for temporary variables (unless you know better, or the numerical range of int is not sufficient), as this will typically produce the best performance and best code size.

It's a crappy rule of thumb, but I admit following it too; out of habit, and laziness to change.

The actually good and portable solution to achieve the optimal performance is to pick the smallest (u)int_fastXX_t. For example, if the loop variable runs from 0 to 100, use int_fast8_t, and it will provide better performance on 8-bit architectures, because int would be 16-bits. Yet, compiler would use 32-bit int on a 32-bit architecture, fitting one native register and not having to perform bit masking operations.

But writing int_fast16_t is more work than just writing int, I admit.
 
The following users thanked this post: Nominal Animal

Offline Kalvin

  • Super Contributor
  • ***
  • Posts: 2145
  • Country: fi
  • Embedded SW/HW.
As a rule of a thumb it is recommended to use int in loops and for temporary variables (unless you know better, or the numerical range of int is not sufficient), as this will typically produce the best performance and best code size.

It's a crappy rule of thumb, but I admit following it too; out of habit, and laziness to change.

I do know that the given rule of thumb is not optimal like you pointed out, but personally I consider that pretty good rule of thumb and compromise for everyday C programming, even in the embedded world.

“Premature optimization is the root of all evil”
- Donald Knuth
« Last Edit: December 27, 2022, 02:22:51 pm by Kalvin »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf