Как мога да гарантирам безопасността на типа на променливи аргументи?

В C бих искал да направя функция или макрос, които изглеждат така:

void Log(char* what, ...) 

където ... трябва да са двойки ключ-стойност на const char*. Наистина бих искал код, който не следва това, да избухне по време на компилация. Опитах се да потърся директива __attribute((..)), която направи това, без резултат. Някакви идеи?


person Mark Pauley    schedule 22.01.2014    source източник
comment
Не можеш, съжалявам. Освен това не забравяйте, че ще ви е необходим начин да сигнализирате, че няма повече двойки стойности (с нулева стойност на сигнализатора, например).   -  person zneak    schedule 23.01.2014
comment
Вместо това е по-добре да подадете масив или два масива, няма смисъл да използвате променливи функции, ако знаете от самото начало какви типове трябва да бъдат вашите аргументи.   -  person Crowman    schedule 23.01.2014


Отговори (3)


където ... трябва да са двойки ключ-стойност на const char*

Тогава изобщо не искате различни аргументи. Ако знаете типа(овете), които очаквате предварително, тогава нямате нужда от него и просто правите нещата по-трудни, отколкото трябва.

Просто предайте в масив.

void Log(const char *what, const char **args, size_t size);
person Ed S.    schedule 22.01.2014
comment
Интересно, наистина бях настроен да използвам variadic log функция тук. Наистина ми харесва вашата гледна точка обаче и може би току-що сте разрешили друг проблем за мен (а именно как да правя запис в стил print-f с метаданни ключ-стойност). - person Mark Pauley; 23.01.2014
comment
@MarkPauley: За това вижте обвивките, които вземат valist. Всички функции, които приемат различни аргументи, трябва да предоставят такъв. - person Ed S.; 23.01.2014

C няма начин да наложи безопасност на типа над произволни променливи аргументи. GCC и Clang имат __attribute__s за някои фиксирани случаи (когато променлива функция очаква последният аргумент да бъде NULL, можете да използвате __sentinel__; или когато това е форматиращ низ, можете да използвате format), но няма общо решение.

От друга страна, C++11 има различни шаблони за решаване на този проблем, но тъй като споменахте, че работите в C, това няма да работи за вас.

person zneak    schedule 22.01.2014
comment
Да, просто ще използвам масив, завършващ с NULL. Вече използвам атрибута format за друга версия на това, но бях помолен да добавя двойки ключ-стойност за метаданни на регистрационния файл. - person Mark Pauley; 23.01.2014

Добре, разбрах заобиколно решение:

#define ASSERT_LIST_TYPE(t, l...) do{t _t[] = {l};}while(0)

void _Log(char* what, ...);

#define Log(what, args...) do{\
  ASSERT_LIST_TYPE(char*, args);\
 _Log(what, args);\
}while(0)

Това поне генерира предупреждение, ако аргументите не са от правилния тип, тъй като инициализацията на фиктивния масив не е от правилния тип.

Планирам #ifdef'ing на ASSERT_LIST_TYPE, ако това не е компилация за отстраняване на грешки за всеки случай..

** Редактиране **

Въз основа на обратната връзка тук промених кода да изглежда така:

void _Log(char* what, const char** args, size_t args_len);

#define Log(what, args...) do{\
  const char* a[] = {args};\
  _Log(what, a, (sizeof a)/sizeof(char*));
}while(0)

Изглежда, че това работи дори ако args е празен и хваща някой, който предава obj-c литерал низ вместо (@"..." вместо "..."), което ме ухапа преди.

person Mark Pauley    schedule 23.01.2014
comment
Внимавайте с това: той ще оцени двойно вашите аргументи, веднъж за инициализацията на масива и веднъж, когато ги подадете към функцията. Ако предавате само локални стойности и изрази без странични ефекти, всичко е наред, но ако направо предавате извикване на функция, може да имате неприятни проблеми, които ще бъдат по-трудни за диагностициране, отколкото използването на грешен тип в извикване на променлива функция. - person zneak; 23.01.2014