Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Last active October 24, 2018 21:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChunMinChang/e8909506cfca774f623fc375fc8ee1d2 to your computer and use it in GitHub Desktop.
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
#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;
}
#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
#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;
}
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
#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;
}
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