Different behavior about args' align between `extern fn` and C

I'm learning about memory alignments, and I found the difference about arguments.

With Windows 11 64bits, long long as the pointer size.

rs

With rustc 1.75.0-nightly (d627cf07c 2023-10-10):

  extern "cdecl" fn a(a:usize,b:u8,c:u8) {
    println!("a:{:p} b:{:p} c:{:p}",&a,&b,&c);
    unsafe{println!("{:x?}",std::slice::from_raw_parts(&a as *const usize as *const u8, 24))}
  };
  a(0x123f,0x55,0x23);
a:0xf1401ff260 b:0xf1401ff26e c:0xf1401ff26f
[3f, 12, 0, 0, 0, 0, 0, 0,
 0, 15, ef, 2c, b5, 1, 55, 23, 
 28, c4, be, 7, f7, 7f, 0, 0]

So both u8 arguments are together at end of the second pointer area.

C

With MSVC cl.exe 19.36.32532 and linker.exe 14.36.32532.0

void __cdecl t(long long a,char b, char c) {
  printf("%X %X %X \n",&a,&b,&c);
  char* bytes = (char*)(&a);
  for (char i = 0; i < 24; i++) {
    printf("%X ",bytes[i]);
  };
}

int main() {
  t(0x123f,0x55,0x23);
};
3F 12 0 0 0 0 0 0 
55 0 0 0 0 0 0 0 
23 0 0 0 0 0 0 0

Is this the expect result?

Your test takes addresses of the variables, which makes it meaningless.

This is because arguments can be passed in registers, and registers don't have an address. So any use of & either forces copying of arguments from registers to stack, or is optimized out and gives a made-up result.

Reading past any single object, like reading 24 bytes after an 8-byte a, is also Undefined Behavior, which means it's completely forbidden to do, and there's no guarantee what will happen when you do it. The compiler is allowed to generate nonsensical/broken/invalid/unreliable code in that case.


If you need to investigate issue with cdecl calls, then you need to only compare values of the arguments passed, not their addresses or memory in between them.

You can also inspect generated assembly, see https://rust.godbolt.org and https://gcc.godbolt.org or cargo asm.

4 Likes

Thanks for your patience to explain for this! It is actually my mistake