I do not know if this is a bug or intended (I'm missing a flag). But in gcc unsigned long
expands to uint32_t
while in llvm it doesn't.
#include <stdint.h>
void test (uint32_t i);
void test (unsigned long j);
int main (void) {}
riscv64-elf-gcc -march=rv32imac_zicsr -mabi=ilp32 -msmall-data-limit=8 -o out main.c
success compile
clang -c -target riscv32-none-elf -march=rv32imaczicsr -mabi=ilp32 -msmall-data-limit=8 -o out main.c
:
main.c:5:6: error: conflicting types for 'test'
5 | void test (unsigned long j);
| ^
main.c:3:6: note: previous declaration is here
3 | void test (uint32_t i);
| ^
1 error generated.
I can see gcc is correct according to:
In both RV32 and RV64 C compilers, the C type int is 32 bits wide. longs and pointers, on the other hand, are both as wide as a integer register, so in RV32, both are 32 bits wide, while in RV64, both are 64 bits wide.
Am I missing a flag? Thank you.
clangd version 19.1.7
I do not know if this is a bug or intended (I'm missing a flag). But in gcc
unsigned long
expands touint32_t
while in llvm it doesn't.
It's a feature, not a bug. The C language specifications allow leeway for implementations to choose the size and representation of the standard integer types, such as unsigned long
, within limits. This allows selection of types that are natural for the execution environment. It follows that the explicit-width types will correspond to different standard types in some implementations than they do in others.
In implementations for 64-bit environments, it is common for unsigned long
to have 64 value bits and no padding, though 64-bit Windows is a notable exception. In implementations for 32-bit environments, it is typical for unsigned long
to have 32 value bits and no padding, which often is the same as unsigned int
in those environments.
Am I missing a flag?
It appears not.
Some C compilers do have flags that affect the sizes of the standard integer types. That may or may not also affect the definition of any particular explicit-width integer type.
To the best of my knowledge, GCC always uses data-type sizes consistent with a specific ABI, but for some architectures, it offers a choice of ABI. The -mabi=ilp32
flag you show in the question is its mechanism for specifying target ABI explicitly, so you are not missing it. Available ABI choices vary with the target architecture.
Clang accepts many of the same options as GCC, but not all of them. It does not seem to document -mabi
as an accepted option. Your experiment seems to show it accepting the option, but that does not mean it elicits analogous behavior from Clang. Nor does Clang seem to document any other option for the purpose.
Note also that
You're not altogether free to choose ABI as you like. If you depend on any external libraries -- probably including the C standard library -- then they must all be built for the same ABI.
Data type sizes are not the end of the story. The C rules for compatible integer types are not based on size or representation. unsigned long
and unsigned int
are not compatible types by C's definition, even if they have identical size and representation. Choosing an ABI that features 32-bit long
s does not necessarily get you uint32_t
being the same as unsigned long
.
Overall, portable code cannot freely intermingle the standard integer types with the explicit-width integer types. It cannot safely assume that any given explicit-width type is the same as any particular standard integer type. It can convert between one and the other, but it must do so carefully and intentionally to ensure that data are not lost or corrupted.