Skip to content

Wrong offset of (small) members and size of subclass when using virtual methods #1516

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

Closed
karroffel opened this issue Feb 4, 2019 · 2 comments

Comments

@karroffel
Copy link

karroffel commented Feb 4, 2019

A base-class with virtual methods might mess up the alignment of a sub-class' members (as well as size).

(the tests fail before the alignment check, but if it's commented out those fail as well)

Apparently this only happens when the base-class has members itself (and are smaller than sizeof(void *) it seems. Padding problems?)

Input C/C++ Header

struct Thing {
    int a;
    virtual void noop() {}
};

struct OtherThing : public Thing {
    int b;
};

Bindgen Invocation

bindgen::Builder::default()
    .header("test.hpp")
    .generate()
    .unwrap()

Actual Results

thread 'bindgen_test::bindgen_test_layout_OtherTest' panicked at 'assertion failed: `(left == right)`
  left: `24`,
 right: `16`: Size of: OtherTest', ...project/target/debug/build/my-project-238c3bd8fa97c21f/out/bindings.rs:37:5
thread 'bindgen_test::bindgen_test_layout_OtherThing' panicked at 'assertion failed: `(left == right)`
  left: `16`,
 right: `12`: Offset of field: OtherThing::b', ...project/target/debug/build/my-project-238c3bd8fa97c21f/out/bindings.rs:49:5

Full generated code:

/* automatically generated by rust-bindgen */

#[repr(C)]
pub struct Thing__bindgen_vtable(::std::os::raw::c_void);
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Thing {
    pub vtable_: *const Thing__bindgen_vtable,
    pub a: ::std::os::raw::c_int,
}
#[test]
fn bindgen_test_layout_Thing() {
    assert_eq!(
        ::std::mem::size_of::<Thing>(),
        16usize,
        concat!("Size of: ", stringify!(Thing))
    );
    assert_eq!(
        ::std::mem::align_of::<Thing>(),
        8usize,
        concat!("Alignment of ", stringify!(Thing))
    );
    assert_eq!(
        unsafe { &(*(::std::ptr::null::<Thing>())).a as *const _ as usize },
        8usize,
        concat!("Offset of field: ", stringify!(Thing), "::", stringify!(a))
    );
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct OtherThing {
    pub _base: Thing,
    pub b: ::std::os::raw::c_int,
}
#[test]
fn bindgen_test_layout_OtherThing() {
    assert_eq!(
        ::std::mem::size_of::<OtherThing>(),
        16usize,
        concat!("Size of: ", stringify!(OtherThing))
    );
    assert_eq!(
        ::std::mem::align_of::<OtherThing>(),
        8usize,
        concat!("Alignment of ", stringify!(OtherThing))
    );
    assert_eq!(
        unsafe { &(*(::std::ptr::null::<OtherThing>())).b as *const _ as usize },
        12usize,
        concat!(
            "Offset of field: ",
            stringify!(OtherThing),
            "::",
            stringify!(b)
        )
    );
}

Expected Results

It is reported that Thing has a size of 16, which makes sense since the vtable is 8 bytes (on my system), the int a is 4 bytes but the whole struct has an alignment of 8, so 4 bytes of padding are added.

The generated code for OtherThing is

pub struct OtherThing {
    pub _base: Thing,
    pub b: ::std::os::raw::c_int,
}

which includes the 4 bytes of padding inside Thing.

I assume that clang "inlines" the members (and vtable) of the base-class and reuses the padding, while in the Rust code the padding is preserved.

So in C++ the layout for OtherThing would look like this

 0.. 7 vtable
 8..11 a
12..15 b

while rust keeps the padding

 0.. 7 vtable
 8..11 a
12..15 padding
16..19 b
19..23 padding

This is resolved properly when removing the virtual method and adding a void * as the first member of Thing, so maybe somewhere the vtable is not considered properly?

@karroffel
Copy link
Author

The FAQ mentions that inline functions (like noop here) can cause it to not generate binding code.

If the line with the virtual function definition is changed to virtual void noop(); the same problem exists, so this doesn't seem to be the issue.

@emilio
Copy link
Contributor

emilio commented Feb 4, 2019

This one is much harder to fix :(

The issue is that clang and gcc pack the base members on the parent class if they fit in the padding. This is #380.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants