Last active
October 24, 2018 21:29
-
-
Save ChunMinChang/e8909506cfca774f623fc375fc8ee1d2 to your computer and use it in GitHub Desktop.
Using reference/pointer instead of copying to get a variable-sized struct object
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "buffer_list.h" | |
#include <assert.h> // assert | |
// #include <stddef.h> // offsetof, size_t | |
#include <stdlib.h> // calloc, free | |
BufferList* create_buffers(uint32_t numbers) { | |
// Allocate memory for buffers. | |
size_t size = buffer_size(numbers); | |
BufferList* list = (BufferList*) calloc(1, size); | |
assert(list); | |
// Set initial values for buffers. | |
assert(fill_buffers(list, size, numbers)); | |
return list; | |
} | |
void destroy_buffers(BufferList* list) { | |
free((void*) list); | |
} | |
size_t buffer_size(uint32_t numbers) { | |
return offsetof(BufferList, buffers[numbers]); | |
} | |
bool fill_buffers(BufferList* list, size_t size, uint32_t numbers) { | |
assert(list); | |
size_t min_size = buffer_size(numbers); | |
if (size < min_size) { | |
return false; | |
} | |
list->numbers = numbers; | |
for (uint32_t i = 0 ; i < list->numbers ; ++i) { | |
list->buffers[i] = i; | |
} | |
return true; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef BUFFER_LIST | |
#define BUFFER_LIST | |
#include <stdbool.h> // bool | |
#include <stdint.h> // uint8_t, uint32_t | |
#include <stddef.h> // size_t | |
typedef struct { | |
uint32_t numbers; | |
uint8_t buffers[1]; | |
} __attribute__((packed)) BufferList; | |
// Example: | |
// const uint32_t channels = 2; | |
// BufferList* list = create_buffers(channels); | |
// assert(list && list->numbers == channels); | |
// for (uint32_t i = 0 ; i < list->numbers ; ++i) { | |
// assert(list->buffers[i] && list->buffers[i] == i); | |
// } | |
// destroy_buffers(list); | |
// Allocate a BufferList with N buffers whose initial values are same as their | |
// indices, where N is equal to the parameter `numbers`. | |
BufferList* create_buffers(uint32_t numbers); | |
// Deallocate the BufferList. | |
void destroy_buffers(BufferList* list); | |
// If you want to do the memory management by yourself, you can use the | |
// following two functions to do what we do above. | |
// Example: | |
// const uint32_t channels = 2; | |
// size_t size = buffer_size(channels); | |
// BufferList* list = (BufferList*) calloc(1, size); | |
// assert(fill_buffers(list, size, channels)); | |
// assert(list->numbers == channels); | |
// for (uint32_t i = 0 ; i < list->numbers ; ++i) { | |
// assert(list->buffers[i] && list->buffers[i] == i); | |
// } | |
// free((void*) list); | |
// Get the size for a BufferList data with `numbers` channels. | |
size_t buffer_size(uint32_t numbers); | |
// Assign values to a BufferList. The values are same as their indices. | |
bool fill_buffers(BufferList* list, size_t size, uint32_t numbers); | |
#endif // BUFFER_LIST |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <assert.h> // for assert | |
#include <stddef.h> // for offsetof | |
#include <stdio.h> // for printf | |
#include <stdlib.h> // for mallocf | |
#define field_offset(type, field) ((size_t) &((type *) 0)->field) | |
#define container_pointer(ptr, type, member) ({ \ | |
const typeof( ((type *)0)->member ) *__mptr = (ptr); \ | |
(type *)( (char *)__mptr - field_offset(type,member) );}) | |
// No Memory Alignment with GCC | |
typedef struct { | |
int a; // 4 bytes | |
double b[1]; // 8 bytes | |
} __attribute__((packed)) Foo; | |
// Memory Alignment with GCC(Non-packed) | |
// typedef struct { | |
// int a; // 4 bytes | |
// double b[1]; // 8 bytes | |
// } Foo; | |
int main() | |
{ | |
printf("size of Foo: %lu\n", sizeof(Foo)); | |
assert(sizeof(Foo) == sizeof(int) + sizeof(double)); | |
// Foo | |
// +------+ <--- 0 | |
// | a | | |
// +------+ | |
// | b | | |
// | | | |
// +------+ | |
Foo* f = 0; | |
printf("address of f: %p\n", f); | |
printf("address of f->a: %p\n", &f->a); | |
// Foo | |
// +------+ <--- 0 ^ | |
// | a | | 4 | |
// +------+ <--- 4 v | |
// | b[0] | | |
// | | | |
// +------+ | |
// so &f->b[0] - f = &f->b[0] = 4 = sizeof(a) | |
printf("address of f->b: %p\n", &f->b); | |
printf("address of f->b[0]: %p\n", &f->b[0]); | |
// Foo | |
// +------+ <--- 0 | |
// | a | | |
// +------+ <--- 4 ^ | |
// | b[0] | | 8 | |
// | | | | |
// +------+ <--- 12 v | |
// | b[1] | | |
// | | | |
// +------+ | |
// so &f->b[1] - f = &f->b[1] = 12 = sizeof(a) + 1 * sizeof(b) | |
printf("address of f->b[1]: %p\n", &f->b[1]); | |
// Foo | |
// +------+ <--- 0 | |
// | a | | |
// +------+ <--- 4 | |
// | b[0] | | |
// | | | |
// +------+ <--- 12 ^ | |
// | b[1] | | 8 | |
// | | | | |
// +------+ <--- 20 v | |
// | b[2] | | |
// | | | |
// +------+ | |
// so &f->b[2] - f = &f->b[2] = 20 = sizeof(a) + 2 * sizeof(b) | |
printf("address of f->b[2]: %p\n", &f->b[2]); | |
// In the same way, the sizeof(a) + N * sizeof(b) = &f->b[N], so we can use | |
// field_offset(Foo, b[N]) to allocate an struct Foo with N double elements. | |
// field_offset interprets address 0 as a pointer for struct Foo and use | |
// the above offset trick to calculate the size of Foo's field, | |
// 'a' and N numbers of 'b'. | |
unsigned int N = 5, size = 0; | |
size = sizeof(Foo) + sizeof(double) * (N-1); | |
assert(N >=0 && field_offset(Foo, b[N]) == size); | |
printf("Allocate struct Foo with %d double members:\n", N); | |
Foo* p = (Foo*) calloc(1, field_offset(Foo, b[N])); | |
p->a = N; | |
unsigned int i = 0; | |
for (i = 0 ; i < N ; ++i) { | |
p->b[i] = (double)i; | |
printf("p->b[%d] = %lf\n", i, p->b[i]); | |
} | |
// p->b[N] = 1.0; // Overflow! | |
for (i = 0 ; i < N ; ++i) { | |
f = container_pointer(&p->b[i], Foo, b[i]); | |
printf("container of %p is %p\n", &p->b[i], f); | |
assert(f == p); | |
} | |
// Actually, field_offset does the same thing as what the offsetof | |
// defined in stddef.h does. | |
for (i = 0 ; i < N ; ++i) { | |
assert(field_offset(Foo, b[N]) == offsetof(Foo, b[N])); | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
all: | |
gcc -o field_offset field_offset.c | |
./field_offset | |
# static library: | |
gcc -c buffer_list.c -o buffer_list.o | |
ar rcs libbufferlist.a buffer_list.o | |
# sample in C: | |
gcc sample.c -L. -lbufferlist -o sample-c | |
./sample-c | |
# sample in Rust: | |
rustc sample.rs -o sample-rs -L. | |
LD_LIBRARY_PATH=. RUST_BACKTRACE=1 ./sample-rs | |
clean: | |
rm field_offset | |
rm buffer_list.o libbufferlist.a | |
rm sample-c | |
rm sample-rs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "buffer_list.h" | |
#include <assert.h> // assert | |
#include <stdio.h> // printf | |
void test_ok() { | |
printf("%s\n", __func__); | |
BufferList* list = create_buffers(10); | |
for (uint32_t i = 0 ; i < list->numbers ; ++i) { | |
printf("list->buffers[%d] = %d\n", i, list->buffers[i]); | |
assert(i == list->buffers[i]); | |
} | |
destroy_buffers(list); | |
} | |
void test_error() { | |
printf("%s\n", __func__); | |
BufferList* list_ptr = create_buffers(10); | |
BufferList list = *list_ptr; | |
assert(&list != list_ptr); // `list` is a clone for `*list_ptr`! | |
assert(&list.buffers != list_ptr->buffers); | |
assert(sizeof(list) == sizeof(uint32_t) + sizeof(uint8_t)); | |
// The following code is very likely to show wrong infomation since | |
// printing list.buffers[i] will be undefined when i > 0. | |
// The `list_ptr` actually gives a memory with one uint32_t data and | |
// ten uint8_t data. However, the `list` object is a struct data with | |
// one uint32_t data and one uint8_t data, since it just a clone of | |
// `*list_ptr`. Therefore, `list.buffers[1]` is out of its bound so | |
// we may get a random value. | |
for (uint32_t i = 0 ; i < list.numbers ; ++i) { | |
// list.buffers[i] is out of the bound of list.buffers for i > 0. | |
// However, C compiler doesn't check this. | |
printf("list.buffers[%d] = %d\n", i, list.buffers[i]); | |
// assert(i == list.buffers[i]); // This is very likely to fail! | |
} | |
destroy_buffers(list_ptr); | |
} | |
int main() { | |
test_ok(); | |
test_error(); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::slice; | |
use std::mem; | |
// A module containing all the types and APIs in the external library. | |
mod sys { | |
// To dereference a raw pointer, allow `Clone` and `Copy`. | |
#[derive(Clone, Copy, Debug)] | |
#[repr(C, packed)] | |
pub struct BufferList { | |
pub numbers: u32, | |
pub buffers: [u8; 1] | |
} | |
#[link(name = "bufferlist")] // libbufferlist | |
extern "C" { | |
pub fn create_buffers(numbers: u32) -> *mut BufferList; | |
pub fn destroy_buffers(list: *mut BufferList); | |
pub fn buffer_size(numbers: u32) -> usize; | |
pub fn fill_buffers(list: *mut BufferList, size: usize, numbers: u32) -> bool; | |
} | |
} | |
fn test_ok1() { | |
println!("test_ok 1"); | |
let channels = 10; | |
let size = unsafe { | |
sys::buffer_size(channels) | |
}; | |
// Allocate buffer with size. | |
let mut data = vec![b'\x00'; size]; | |
let ptr = data.as_mut_ptr() as *mut sys::BufferList; | |
unsafe { | |
sys::fill_buffers(ptr, size, channels); | |
} | |
let list: &sys::BufferList = unsafe { &(*ptr) }; | |
// `list` is a reference to the object pointed by `ptr`. | |
println!("list reference to {:p}, ptr: {:p}", list, ptr); | |
println!("list.numbers: {}, list buffer len: {}", | |
list.numbers, list.buffers.len()); | |
assert_eq!( | |
list as *const sys::BufferList, | |
ptr | |
); | |
assert_eq!( | |
channels, | |
list.numbers | |
); | |
assert_eq!( | |
mem::size_of_val(list), | |
mem::size_of::<u32>() + mem::size_of::<u8>() | |
); | |
assert_eq!( | |
list.buffers.len(), | |
1 | |
); | |
assert!(channels < 2 || list.numbers as usize > list.buffers.len()); | |
// compile error since the index is out of bounds! | |
// let x = list.buffers[1]; | |
assert!(mem::size_of_val(&data) > mem::size_of_val(list)); | |
// Although the index is out of bound, but its ok to make a | |
// slice by `list.numbers` since we allocate enough memory | |
// to contain 10 buffers! | |
let p = list.buffers.as_ptr() as *const u8; | |
let len = list.numbers as usize; | |
let buffers = unsafe { | |
slice::from_raw_parts(p, len) | |
}; | |
for (i, buffer) in buffers.iter().enumerate() { | |
println!("buffers[{}] = {}", i, buffer); | |
assert_eq!(&(i as u8), buffer); | |
} | |
} | |
fn test_error1() { | |
println!("test_error 1"); | |
let channels = 10; | |
let size = unsafe { | |
sys::buffer_size(channels) | |
}; | |
// Allocate buffer with size. | |
let mut data = vec![b'\x00'; size]; | |
let ptr = data.as_mut_ptr() as *mut sys::BufferList; | |
unsafe { | |
sys::fill_buffers(ptr, size, channels); | |
} | |
let list: sys::BufferList = unsafe { *ptr }; | |
// `list` is a clone for `*ptr`! | |
println!("list @ {:p}, ptr: {:p}", &list, ptr); | |
println!("list.numbers: {}, list buffer len: {}", | |
list.numbers, list.buffers.len()); | |
assert_ne!( | |
&list as *const sys::BufferList, | |
ptr | |
); | |
assert_eq!( | |
mem::size_of_val(&list), | |
mem::size_of::<u32>() + mem::size_of::<u8>() | |
); | |
assert_eq!( | |
channels, | |
list.numbers | |
); | |
assert_eq!( | |
list.buffers.len(), | |
1 | |
); | |
assert!(channels < 2 || list.numbers as usize > list.buffers.len()); | |
// compile error since the index is out of bounds! | |
// let x = list.buffers[1]; | |
// The following code is very likely to show wrong infomation since | |
// printing list.buffers[i] will be undefined when i > 0. | |
// The `ptr` actually gives a memory with one u32 data and ten u8 data. | |
// However, the `list` object is a struct data with one u32 data and | |
// one u8 data, since it just a clone of `*ptr`. Therefore, | |
// `list.buffers[1]` is out of its bound so we may get a random value. | |
let p = list.buffers.as_ptr() as *const u8; | |
let len = list.numbers as usize; | |
let buffers = unsafe { | |
slice::from_raw_parts(p, len) | |
}; | |
for (i, buffer) in buffers.iter().enumerate() { | |
println!("buffers[{}] = {}", i, buffer); | |
// assert_eq!(&(i as u8), buffer); // This is very likely to fail! | |
} | |
} | |
fn test_ok2() { | |
println!("test_ok 2"); | |
let channels = 10; | |
let ptr = unsafe { | |
sys::create_buffers(channels) | |
}; | |
let list: &mut sys::BufferList = unsafe { | |
&mut (*ptr) | |
}; | |
// `list` is a reference to the object pointed by `ptr`. | |
println!("list reference to {:p}, ptr: {:p}", list, ptr); | |
println!("list.numbers: {}, list buffer len: {}", | |
list.numbers, list.buffers.len()); | |
assert_eq!( | |
list as *const sys::BufferList, | |
ptr | |
); | |
assert_eq!( | |
channels, | |
list.numbers | |
); | |
assert_eq!( | |
mem::size_of_val(list), | |
mem::size_of::<u32>() + mem::size_of::<u8>() | |
); | |
assert_eq!( | |
list.buffers.len(), | |
1 | |
); | |
assert!(channels < 2 || list.numbers as usize > list.buffers.len()); | |
// compile error since the index is out of bounds! | |
// let x = list.buffers[1]; | |
// Although the index is out of bound, but its ok to make a | |
// slice by `list.numbers` since the extranl API, create_buffers, | |
// allocates enough memory to contain 10 buffers! | |
let buffers = unsafe { | |
let ptr = list.buffers.as_ptr() as *const u8; | |
let len = list.numbers as usize; | |
slice::from_raw_parts(ptr, len) | |
}; | |
for (i, buffer) in buffers.iter().enumerate() { | |
println!("buffers[{}] = {}", i, buffer); | |
assert_eq!(&(i as u8), buffer); | |
} | |
unsafe { | |
sys::destroy_buffers(list); | |
} | |
} | |
fn test_error2() { | |
println!("test_error 2"); | |
let channels = 10; | |
let ptr = unsafe { | |
sys::create_buffers(channels) | |
}; | |
let list: sys::BufferList = unsafe { | |
*ptr | |
}; | |
// `list` is a clone for `*ptr`! | |
println!("list @ {:p}, ptr: {:p}", &list, ptr); | |
println!("list.numbers: {}, list buffer len: {}", | |
list.numbers, list.buffers.len()); | |
assert_ne!( | |
&list as *const sys::BufferList, | |
ptr | |
); | |
assert_eq!( | |
mem::size_of_val(&list), | |
mem::size_of::<u32>() + mem::size_of::<u8>() | |
); | |
assert_eq!( | |
channels, | |
list.numbers | |
); | |
assert_eq!( | |
list.buffers.len(), | |
1 | |
); | |
assert!(channels < 2 || list.numbers as usize > list.buffers.len()); | |
// compile error since the index is out of bounds! | |
// let x = list.buffers[1]; | |
// The following code is very likely to show wrong infomation since | |
// printing list.buffers[i] will be undefined when i > 0. | |
// The `ptr` actually gives a memory with one u32 data and ten u8 data. | |
// However, the `list` object is a struct data with one u32 data and | |
// one u8 data, since it just a clone of `*ptr`. Therefore, | |
// `list.buffers[1]` is out of its bound so we may get a random value. | |
let buffers = unsafe { | |
let p = list.buffers.as_ptr() as *const u8; | |
let len = list.numbers as usize; | |
slice::from_raw_parts(p, len) | |
}; | |
for (i, buffer) in buffers.iter().enumerate() { | |
println!("buffers[{}] = {}", i, buffer); | |
// assert_eq!(&(i as u8), buffer); // This is very likely to fail! | |
} | |
unsafe { | |
sys::destroy_buffers(ptr); | |
} | |
} | |
// An adapter layer to call APIs in the external library. | |
// mod ext { | |
// use super::sys; | |
// struct Buffers { | |
// } | |
// } | |
fn main() { | |
test_ok1(); | |
test_error1(); | |
test_ok2(); | |
test_error2(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment