-
Notifications
You must be signed in to change notification settings - Fork 318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wrong ABI for fixed-size array parameters #171
Comments
Why? There is nothing wrong with that declaration, except that it's a bit more verbose, but at least will explicitly match Rust's side. |
No, they are not equivalent =) This C declaration void with_array(double foo[3]); Is equivalent to pub unsafe extern fn with_array(foo: &[f64; 3]) Both functions actually take a (tin) pointer here. EDIT: void with_array(double (*foo)[3]); Is somehow equivalent (except for the thin/fat pointer issue) to pub unsafe extern fn with_array(foo: &[[f64; 3]]) pub unsafe extern fn with_array(foo: [f64; 3]) Have no direct equivalent in C. The closest thing I could think of would be something like struct 3Dvector {
double data[3]; // or double x, y, z;
}
void with_array(struct 3Dvector foo) |
They are. In C void with_array(double foo[3]); and void with_array(double (*foo)[3]); and void with_array(double foo[]); all translate to the exactly same memory layout. The difference is only in syntax sugar where in option without |
But, either way, yes, there is no direct equivalent in C for Rust's pass-by-value arrays, and you need to use reference in Rust function definition if you want to interact with C code. |
Sorry, but GCC disagree with you 😃 inline void with_array_1(double foo[3]) {}
inline void with_array_2(double (*foo)[3]) {}
inline void with_array_3(double foo[]) {}
int main() {
double array[3] = {3, 4, 5};
with_array_1(array);
with_array_2(array);
with_array_3(array);
return 0;
} Produces
In C++ mode (using the C API from C++), this is an hard error:
|
@Luthaf It doesn't. as I said:
We're talking about memory layout here, while you're trying to compare the C syntax. Instead, for FFI compatibility what really matter is not whether you add explicit |
Btw, this is not true on the Rust side, and there you should always use explicit |
OK, I get what you mean. My initial issue was more related to passing For |
Shouldn't cbindgen error when trying to create a binding for such a function? There's no way to do it correctly, and what it currently does will trigger undefined behavior (which, in this case, is almost certainly not going to be the intended behavior). Or at the very least, produce a warning. But it should probably be an error. |
Yeah, a warning/error would be good (even better would be if Rust compiler would do the same). |
There seems to be some debate about whether this sort of thing should have a compiler warning (rust-lang/rust#19834 and rust-lang/rust#36464). Though I don't entirely see why it wouldn't. It should be a clippy lint at least. |
To add to this discussion (very late), I agree that a warning/error would be good here. Leaning towards an error. |
For anybody who stumbles across this like I did, I had to run some examples to wrap my head around the references part of this. In C,
In Rust, Based on this, you could kind of make the argument that bindgen could make Here's the code that unstuck me if it helps anyone: unsafe fn takes_ptr(a: *const u8) {
print!("takes_ptr: ");
for i in 0..16 {
// prints:
// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
// 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
print!("{:#04x}, ", unsafe { *a.add(i) });
}
println!();
}
unsafe fn takes_cbindgen_format(a: *const [u8; 16]) {
print!("takes_cbg: ");
for i in 0..2 {
// prints: 0x0706050403020100, 0x0000564d9e6e4110,
// second value is random (from beyond array bounds)
print!("{:#018x}, ", unsafe { *a.add(i).cast::<u64>() });
}
println!();
}
fn main() {
let arr: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
println!("{}", std::mem::size_of::<[u8; 16]>()); // 16
println!("{}", std::mem::size_of::<&[u8; 16]>()); // 8; thin pointer
println!("{:?}", arr); // prints original array, starting at 0
let ref_ptr: *const [u8; 16] = &arr;
let first: *const u8 = arr.as_ptr();
unsafe {
takes_ptr(first);
takes_cbindgen_format(ref_ptr);
}
} #include<stdio.h>
#include<stdint.h>
int takes_ptr(char *a) {
printf("takes_ptr: ");
for (int i=0; i<16; ++i)
printf("0x%02x, ", *(a + i)); // prints 0..15
printf("\n");
}
int takes_arr_thing(char a[]) {
printf("takes_arr: ");
for (int i=0; i<16; ++i)
printf("0x%02x, ", *(a + i)); // prints 0..15
printf("\n");
}
int takes_cbindgen_format(char (*a)[16]) {
printf("takes_cbg: ");
for (int i=0; i<2; ++i)
// prints 0x0000000003020100, 0x0000000000401260, the second value is random
// (it is one beyond the array's end)
printf("0x%016lx, ", *(uint64_t*)(a + i));
printf("\n");
}
int main() {
char arr[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
printf("%d\n", sizeof(char[16]));
takes_ptr(arr);
takes_arr_thing(arr);
takes_cbindgen_format(&arr); // note need to pass reference
} |
It looks like rust uses pass by value for fixed-size array (
[f64;3]
), but C uses pass by reference (pointer to the first element).Which means a functions like
will be translated as
which is incorrect, as rust will look for 3 values in the parameters, and not a single pointer.
If I correct the rust function to match the C prototype as
cbindgen will emit the wrong declaration:
The text was updated successfully, but these errors were encountered: