Не говорите, что писали нечто вроде:
uint8_t * a = malloc(100);
*(a + 2*10 + 4) = 42; // a[2][4]=42
На самом деле можно сделать нормальный указатель и пользоваться выделенной памятью как массивом:
uint8_t (* a)[10][10] = malloc(100);
(*a)[2][4] = 42
Особенно классно, что можно объявлять массивы переменной длины
uint8_t (* a)[x][y][z] = malloc(sizeof(*a));
#c #cpp
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥1
Представим, что мы пишем некую обобщенную инициализацию с использованием макроса generic.
void mod1_init(inst1_t*, cfg_t*);
void mod2_init(inst2_t*, cfg_t*);
#define mod_init(x,c) _Generic((x),\
inst1_t*: mod1_init, \
inst2_t*: mod2_init)(x, c)
Далее когда будем пользоваться просто вызываем наш макрос с нужными параметрами и всё само подставляется.
cfg_t cfg = {
.f=1,
.d=3,
};
mod_init(&inst, &cfg);
Ровно до тех пор, пока мы не захотим передать указатель на составной литерал.
mod_init(&inst, &(cfg_t){
.f=1,
.d=3,
});
Код собираться не будет, а ведь это могла быть вполне валидная конструкция, будь здесь не макрос, а простая функция.
Что бы исправить ситуацию введём в макрос вариадик аргумент, но без всяких "
__VA_ARGS__
"#define mod_init(x,c...) _Generic((x),\
inst1_t*: mod1_init,\
inst2_t*: mod2_init)(x, c)
Заметили три точки после параметра "
c
"?Есть только одно НО: это не стандарт, а лишь расширение GNU 🤷♂️
https://cdeblog.ru/variadic-macro-with-generic
#c #cpp
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🤔1🤩1👌1
Хотелось бы озвучить свое мнение о данном вопросе.
Речь пойдет о передачи каких-либо параметров в функцию и возврате указателей различных типов.
Например такие:
// Пример (1)
uint32_t * p = (uint32_t *) malloc(1245);
// Пример (2)
uint32_t (* arr)[32] = malloc(sizeof(* arr));
void * init_fq(void* init_arr);
typedef struct data_t {
void * f1;
data_item_t * f2;
volatile void * f3;
} data_t;
data_t var_s = {
.f1 = (void *)init_fq((void *)(*arr)),
.f2 = (data_item_t *)malloc(sizeof(data_item_t)),
.f3 = &volatile_array,
};
// Пример (3) с потерей квалификаторов
uint32_t * p1 = (void *)var_s.f3; // теряем volatile
С одной стороны кажется, что ни чего плохого в данном коде нет, возможно кроме некоторых специфичных конструкций, ок которых писал ранее.
При возврате из аллокатора нет смысла кастовать, так как зачастую все аллокаторы возвращают именно
void*
, а все дополнительные махинации лишь добавляют в код грязи (Пример 1)Случай 2 уже не так безобиден. Пока у нас поле
f1
структуры имеет тип void*
всё хорошо и работает так же как предыдущий случай. Но может настать момент когда тип поля структуры изменится, а возвращаемое значение изменится на что-то иное, например перестанет быть указателем, или станет указателем совсем другого типа, в таком случае не исключается вариант конфликта типов, который не будет подсвечен из-за принудительного каста.Другим нехорошим обстоятельством (Пример 3) может быть потеря квалификаторов
volatile
и const
, а вот это уже может быть большой проблемой. Начиная от неопределенного поведения и заканчивая падениями в рантайме, не понятно что хуже.Перепишем код и избавимся от лишних вещей:
uint32_t * p = malloc(1245);
data_t var_s = {
.f1 = init_fq(*arr),
.f2 = malloc(sizeof(data_item_t)),
.f3 = &volatile_array,
};
volatile uint32_t * p1 = var_s.f3; // теряем volatile
Стоит сказать, что кастование одних типов к другим весьма нехорошая практика, которая потенциально приносит проблемы и грязь в код.
Есть еще одна мысль на счет универсальных функций вроде аллокаторов или функций вида
send()
возвращаемые значения и типы входных аргументов оформлять как void*
. Таким образом и код будет чище и сопровождать его проще.// Пример с приемом и возвратом uint8_t*
size_t sent_data_u8(uint8_t * data, size_t size);
size_t sent_data_pv(void * data, size_t size);
data_t data_s = { ... };
uint8_t data_arr[32] = { ... };
uint32_t data_32 = 42;
sent_data_u8((uint8_t *)&data_s, sizeof(data_s));
sent_data_u8((uint8_t *)data_arr, sizeof(data_arr));
sent_data_u8((uint8_t *)&data_32, sizeof(data_32));
sent_data_pv(&data_s, sizeof(data_s));
sent_data_pv(data_arr, sizeof(data_arr));
sent_data_pv(&data_32, sizeof(data_32));
Мне кажется выводы сделать просто.
https://cdeblog.ru/void-pointers-casts
Если у вас другое мнение, готов обсудить и даже принять
#c #cpp
Please open Telegram to view this post
VIEW IN TELEGRAM