У меня есть функция с аргументом uint32_t *
, который на самом деле указывает на 64-битное значение. Я хочу, чтобы вокруг него был макрос, который принимает uint64_t
в качестве входных данных.
Может ли макрос расширяться по-разному в зависимости от типа входного аргумента?
Приведенный ниже макрос отлично подходит для моего случая; однако это неэффективно:
void func(uint32_t* a);
#define func_wrapper(a) do { \
uint64_t aa = a; func((uint32_t*) &aa); \
} while(0)
Например, случай 1 не так эффективен, как случай 3 ниже.
uint64_t x = 12;
func_wrapper(x) // case 1
func_wrapper(12) // case 2
func((uint32_t*) &x); // case 3
Есть ли способ определить макрос, который расширяется до
#define func_wrapper(a) { \
func((uint32_t*) &(a)); \
}
когда аргумент не является литералом и расширяется до
#define func_wrapper(a) { \
uint64_t aa = a; func((uint32_t*) &(aa)); \
}
когда он является?
В стандартном C нет способа динамической обработки типов. GCC имеет расширение typeof
, см. gcc.gnu.org/onlinedocs/gcc/extensions-to-the-c-language-family/…. Вы также можете использовать общие функции.
«У меня есть функция, которая имеет uint32_t* в качестве аргумента, который на самом деле указывает на 64-битное значение» не имеет отношения к вопросу и почти наверняка имеет плохой дизайн. Это не имеет значения, потому что проблема, которую вы просите решить, передает &x
вместо foo_wrapper(x)
и & (uint64_t) {12}
вместо foo_wrapper(12)
, и это не связано с вызовом функции. Стандарт C не поддерживает это как часть замены макросов. Оператор _Generic
можно использовать для того, что вы хотите, если аргументом макроса всегда является либо переменная uint64_t
, либо литерал int
.
Это плохой дизайн, потому что доступ к uint64_t
через uint32_t
не определен стандартом C, и почти наверняка есть лучший и определенный способ делать то, что делает func
.
Макросы C полностью обрабатываются на этапе предварительной обработки, в котором отсутствует информация о типах, и они не могут расширяться по-разному на основе концепций языка, таких как типы. Язык C предоставляет очень мало инструментов для полиморфизма, что, кажется, именно то, о чем вы действительно просите.
Также стоит сказать, что доступ к значению с эффективным типом uint64_t
через указатель типа uint32_t*
нарушает правила псевдонимов типов C. Это очень плохая идея и может привести ко всем видам неопределенного поведения, особенно при наличии высоких уровней оптимизации компилятора.
Теперь, что касается основного предположения в вашем вопросе, я хотел бы обратиться к этому утверждению:
Например, вариант 1 не так эффективен, как вариант 3.
Вот ваш код после расширения макроса, где я также исправил нарушение сглаживания типов:
void func(uint64_t* a);
uint64_t x = 12;
uint64_t aa = x; func((uint64_t*) &aa); // case 1
func((uint64_t*) &x); // case 3
Вы не должны предполагать, что случай 3 более эффективен во время выполнения, чем случай 1. Оптимизаторы C способны к очень агрессивной оптимизации, и количество инструкций/циклов в окончательном оптимизированном исполняемом файле имеет лишь слабую корреляцию с количеством операторов в язык С. В этом примере, если компилятор может доказать, что func
не изменяет свой аргумент, он может применить распространение копирования и полностью удалить временную переменную aa
. Предполагая, что свойство истинно, вы потенциально можете помочь анализу сделать это, объявив func
с квалификатором const
, то есть:
void func(const uint64_t* a);
Однако даже при отсутствии какой-либо оптимизации маловероятно, что вы сможете измерить реальную разницу в производительности между любым из них на современном суперскалярном процессоре.
Спасибо. Я использую O2 при компиляции и получаю немного другую выходную сборку (размер кода становится на 2 слова больше в случае 1). Добавление const в объявление не изменило его. Но, как вы сказали, это очень незначительно, я собираюсь проигнорировать это.
Я думаю, вы просите полиморфизм.