CDEblog
77 subscribers
10 photos
5 videos
2 files
81 links
Circuit Design Engineer BLOG

Программирование, схемотехника может быть что-то ещё 🤷🏻‍♂️

Для личного контакта: @devprodest
Блог в интернете: https://cdeblog.ru

Немного о кулинарии: @cdefood
Download Telegram
👩‍💻👩‍💻 Пробовали работать с памятью выделенной в куче как с многомерным массивом?

Не говорите, что писали нечто вроде:

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
👩‍💻👩‍💻 Variadic macro там где его не просили. Однако без него никак.

Представим, что мы пишем некую обобщенную инициализацию с использованием макроса 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
👩‍💻👩‍💻 Не нужно кастовать указатели в void* и void* к другим

Хотелось бы озвучить свое мнение о данном вопросе.

Речь пойдет о передачи каких-либо параметров в функцию и возврате указателей различных типов.

Например такие:
// Пример (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