C++中可移植的调用栈参数入栈实现方案问询
Absolutely doable! The key challenge here is accounting for different calling conventions across your target platforms (Windows, macOS/iOS, Android) and architectures (x86, x86_64, ARM, ARM64), since embedded assembly is inherently tied to how each system expects function arguments to be passed.
Below’s a breakdown of how to implement this with C++ inline assembly, using conditional compilation to target each toolchain:
1. Visual Studio (Windows: x86/x86_64)
Windows uses distinct calling conventions depending on the architecture:
32-bit x86: Most C/C++ functions use
cdecl(caller cleans the stack) orstdcall(callee cleans the stack). For pushing arguments to the stack, you can use MSVC’s__asmblock:// Example: Call int add(int a, int b) using cdecl int a = 5, b = 10; int result; __asm { push b // Push rightmost argument first push a call add // Call the function add esp, 8 // Clean up stack (2 args × 4 bytes) for cdecl mov result, eax // Store return value from EAX }For
stdcall, you’d omit theadd esp, 8line since the callee handles stack cleanup.64-bit x86_64: MSVC uses the
fastcallconvention, where the first 4 integer arguments are passed via registers (rcx,rdx,r8,r9), with remaining args pushed to the stack. Inline assembly support here is limited, so you may want to use a__declspec(naked)function to manually construct the call frame:__declspec(naked) int call_add(int a, int b) { __asm { // Setup args: a in rcx, b in rdx mov rcx, [rsp + 8] // First arg (since return address is at rsp) mov rdx, [rsp + 16] // Second arg call add ret } }
2. Xcode (macOS/iOS: x86_64/ARM64)
Apple’s toolchain uses Clang, which supports GNU-style inline assembly. The calling conventions follow System V standards:
x86_64: First 6 integer args go to
rdi,rsi,rdx,rcx,r8,r9; remaining args are pushed to the stack (right-to-left). Example:int a = 5, b = 10; int result; asm volatile ( "pushq %%rdx\n\t" // Push b (stored in rdx) "pushq %%rdi\n\t" // Push a (stored in rdi) "call _add\n\t" // Call the function (leading underscore is legacy but safe) "addq $16, %%rsp\n" // Clean up stack : "=a"(result) // Return value in rax -> result : "D"(a), "d"(b) // Inputs: a in rdi, b in rdx : "rsp", "memory" // Clobbered registers/memory );ARM64 (Apple Silicon): Uses the AArch64 calling convention, where the first 8 integer args go to
x0–x7, with extra args pushed to the stack. Stack must stay 16-byte aligned:int a = 5, b = 10; int result; asm volatile ( "str %1, [sp, #-16]!\n\t" // Push b to stack, adjust sp (16-byte align) "str %0, [sp, #8]\n\t" // Push a to stack (offset 8) "bl add\n\t" // Call the function "add sp, sp, #16\n" // Restore stack : "=r"(result) // Return value in x0 -> result : "r"(b), "r"(a) // Inputs: b and a in general registers : "sp", "memory" // Clobbered stack and memory );
3. Android Studio (ARM/ARM64/x86/x86_64)
Android uses Clang and follows Linux-style calling conventions, which align closely with Apple’s:
- ARM 32-bit: Uses the APCS convention, where args are pushed right-to-left, with the first 4 args optionally in registers (
r0–r3). For stack-based passing:int a = 5, b = 10; int result; asm volatile ( "push {r1}\n\t" // Push b (in r1) "push {r0}\n\t" // Push a (in r0) "bl add\n\t" // Call function "add sp, sp, #8\n" // Clean stack : "=r"(result) // Return in r0 -> result : "r"(a), "r"(b) : "sp", "memory" ); - ARM64/x86/x86_64: Use the same patterns as the Xcode ARM64/x86_64 examples—just omit the leading underscore on function names (Android doesn’t use this).
Critical Considerations
- Conditional Compilation: Wrap each platform/architecture-specific block in preprocessor directives like
#ifdef _WIN32,#ifdef __APPLE__,#ifdef __ANDROID__,#ifdef __x86_64__, and#ifdef __arm64__to ensure the right code is compiled for each toolchain. - Stack Alignment: All modern architectures require strict stack alignment (16 bytes for x86_64/ARM64, 4 bytes for x86/ARM 32-bit). Failing to align the stack before pushing args will cause crashes.
- Compiler Optimizations: Use
asm volatileto prevent the compiler from optimizing away your assembly block, and explicitly list clobbered registers/memory to avoid conflicts. - Alternative (If Assembly Gets Too Cumbersome): If maintaining inline assembly across all platforms becomes a headache, you could use
libffi(a portable foreign function interface library) to handle argument pushing dynamically. But since you prioritized inline assembly, this is just a backup option.
In short: Yes, it’s feasible. You’ll just need to write architecture/platform-specific inline assembly snippets and wrap them in conditional compilation to support Visual Studio, Xcode, and Android Studio.
内容的提问来源于stack exchange,提问作者Raptisoft

