Is it safe to pass a uint64_t containing a 32-bit value to an external function whose parameter is actually a uint32_t?
Asked Answered
S

2

6

I'm working on a cross-platform program that calls a function from a dynamic library with C linkage. I need to support multiple versions of this dynamic library, but between two of the versions I need to support, there is a function parameter that has changed from uint32_t to uint64_t.

If I pass this function a uint64_t that contains value which is still representable as a uint32_t, is that safe to do even when the function's parameter is actually a uint32_t?

Put more specifically:

If the source of the function as compiled into the dynamic library is:

extern "C" void foo(uint32_t param) {
    ...
}

Is it safe for me to use the function like so:

extern "C" void foo(uint64_t);

uint32_t value32 = 10; // Ensure value can be represented by uint32_t
uint64_t value64 = value32;
foo(value64);

If yes, is it safe to do this across different platforms? This program of mine supports 32-bit and 64-bit Windows (compiled as x86 for both), x86_64 macOS, arm64 macOS, x86 Linux, and x86_64 Linux.

Sally answered 3/5, 2022 at 17:8 Comment(3)
No. You're lying in the second code by telling it the first is something it isn't, exploiting the fact that extern "C" strips param mangling in the identifier name.Destroyer
@Destroyer Can you say more about how this would cause problems, so long as the value being passed in is representable using 32-bits?Sally
Consider what is on the activation stack. And by "what is on the activation stack" I mean (a) what the caller sets up vs. what the callee expects. Frankly, I don't understand what the concern is in the first place, when it would be far-easier to just prototype and create the API to always take a uint64_t and pass a uint32_t or uint64_t caller side, letting the compiler sort out the upscale conversion in the case of the former.Destroyer
R
8

No, this is illegal. C++20 [basic.link] p11:

After all adjustments of types (during which typedefs (9.2.3) are replaced by their definitions), the types specified by all declarations referring to a given variable or function shall be identical.

Moreover, it will actually fail on 32-bit x86 systems using the usual stack-based calling conventions. The function defined with uint32_t will be looking for one dword on the stack, but two will have been pushed. Any arguments above it will then be in the wrong place. It's even worse with the stdcall convention, in which the called function pops its own arguments; it will pop the wrong amount, unbalance the stack, and cause all sorts of mayhem after returning.

Reinsure answered 3/5, 2022 at 17:19 Comment(0)
G
-1

I'm sure someone will pipe in with some contrived example of a system where this won't work, but on Intel systems remember that larger registers are built on top of smaller ones (rax is built over eax, which is built over ax and al), and so what you're trying to do will work.

Groveman answered 3/5, 2022 at 17:12 Comment(9)
Now, for the record, I can't picture a world in which what you're trying to do is the best and clearest way to do it. I'm just saying that you'll be hard-pressed to find an example of a system that will cause your failure. If anything, it's going to be user error, trying to use the larger data type without realizing you're essentially chopping off the upper half.Groveman
Any idea if the same holds true for arm64? I've updated my question to mention that the program will be compiled for arm64 macOS as well as x86_64.Sally
Yeah, it's the same story, X0 is built on W0 in a similar way.Groveman
This would be true when parameters are passed in registers, which is true for the first few integer parameters on x86-64. On x86-32, they are on the stack and are 32 bits, so all hell breaks loose when the type is wrong, as there is an extra dword on the stack where it's not expected.Reinsure
Nobody asked about 32-bit systems, and on 64-bit systems the stack is 64-bit. To later use values off the stack you pop them into registers, which brings you back to my answer. As I said, people like Nate here will find contrived and irrelevant examples where this won't work, but it's really fine, despite his downvotes.Groveman
OP said "Windows". Didn't specify 64 bits.Reinsure
A few lines above he says "Any idea if the same holds true for arm64? I've updated my question to mention that the program will be compiled for arm64 macOS as well as x86_64."Groveman
I have updated my question to be clear about specifically which platforms I need to support. And I do indeed need to support both 32-bit and 64-bit Windows, and in both cases the app is compiled as x86, not x86_64. So I think @NateEldredge's concern is warranted.Sally
I don't have to bend over backwards to find a system where this wouldn't work, (Sun Solaris, PowerPC, network byte ordering, etc...) but: I think it's good to have an answer here that actually speaks to the underlying hardware, which is the point of using C/C++ over other languages. The community should not down-vote correct answers, just because it may promote bad programming style.Uncharitable

© 2022 - 2024 — McMap. All rights reserved.