ti-enxame.com

Detectando expressões constantes inteiras em macros

Houve uma discussão na lista de discussão do kernel do Linux sobre uma macro que testa se seu argumento é uma expressão constante inteira e é uma expressão constante inteira em si.

Uma abordagem particularmente inteligente que não usa built-in, proposta por Martin Uecker (obtendo inspiração de glibc's tgmath.h ) é:

#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))

Essa macro se expande para uma expressão constante inteira do valor 1 se o argumento for uma expressão constante inteira, 0 caso contrário. No entanto, ele depende de sizeof(void) para ser permitido (e diferente de sizeof(int)), que é uma extensão GNU C .

É possível escrever essa macro sem recursos internos e sem depender de extensões de idioma? Se sim, avalia seu argumento?


Para uma explicação da macro mostrada acima, consulte: Macro __is_constexpr Macro do Kernel do Linux (---)

35
Acorn

Use a mesma idéia, em que o tipo de uma expressão ?: depende se um argumento é uma constante de ponteiro nulo ou um void * comum, mas detecte o tipo com _Generic :

#define ICE_P(x) _Generic((1? (void *) ((x)*0) : (int *) 0), int*: 1, void*: 0)

Demo no Ideone._Generic é uma adição do C11, portanto, se você estiver preso no C99 ou algo anterior, não poderá usá-lo.

Além disso, tenha links padrão para a definição de uma constante de ponteiro nulo e a maneira como as constantes de ponteiros nulos interagem com o tipo de uma expressão ?: :

Uma expressão constante inteira com o valor 0, ou uma expressão convertida para digitar void *, é chamada constante de ponteiro nulo.

e

Se o segundo e o terceiro operandos são ponteiros ou um é uma constante de ponteiro nulo e o outro é um ponteiro, o tipo de resultado é um ponteiro para um tipo qualificado com todos os qualificadores de tipo dos tipos mencionados pelos dois operandos. Além disso, se ambos os operandos são ponteiros para tipos compatíveis ou para versões de tipos compatíveis de forma diferente, o tipo de resultado é um ponteiro para uma versão adequadamente qualificada do tipo composto; se um operando for uma constante de ponteiro nulo, o resultado terá o tipo do outro operando; caso contrário, um operando é um ponteiro para cancelar ou uma versão qualificada do void; nesse caso, o tipo de resultado é um ponteiro para uma versão qualificada do void .

25
user2357112

Não tenho uma correção para sizeof(void) não ser padrão, mas você pode contornar a possibilidade de sizeof(void) == sizeof(int) fazendo algo como:

#define ICE_P(x) ( \
  sizeof(void) != \
  sizeof(*( \
    1 ? \
      ((void*) ((x) * 0L) ) : \
      ((struct { char v[sizeof(void) * 2]; } *) 1) \
    ) \
  ) \
)

Eu sei que não é uma resposta completa, mas é um pouco mais perto…

Edit : Pesquisei um pouco sobre quais soluções funcionam em vários compiladores. Eu codifiquei todas as informações a seguir em Hedley ; consulte as macros HEDLEY_IS_CONSTANT, HEDLEY_REQUIRE_CONTEXPR e HEDLEY__IS_CONSTEXPR. É de domínio público e um único cabeçalho; portanto, é muito fácil entrar no seu projeto ou copiar os bits de seu interesse.

Macro e variantes C11

a macro C11 do usuário2357112 deve trabalhar em qualquer compilador C11, mas SunCC e PGI estão quebrados no momento, então você terá que colocá-los na lista negra. Além disso, o IAR define __STDC_VERSION__ no modo C++, e esse truque não funciona em C++ (AFAIK nada funciona ), portanto você provavelmente desejará garantir que __cplusplus isn está definido. Eu verifiquei que ele realmente funciona no GCC, clang (e compiladores derivados de clang como emscripten), ICC, IAR e XL C/C++.

Fora isso, alguns compiladores suportam _Generic mesmo nos modos mais antigos como uma extensão:

  • GCC 4.9 +
  • clang; verifique com __has_feature(c_generic_selections) (no entanto, você pode desativar o aviso -Wc11-extensions)
  • ICC 16.0 +
  • XL C/C++ 12.1 ou superior

Além disso, observe que algumas vezes os compiladores emitem um aviso quando você lança um int para um void*; você pode contornar isso primeiro convertendo para um intptr_t e depois para void*:

#define ICE_P(expr) _Generic((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)

Ou, para compiladores (como o GCC) que definem __INTPTR_TYPE__, você pode usá-lo em vez de intptr_t e não precisa incluir stdint.h.

Outra implementação possível aqui é usar __builtin_types_compatible_p em vez de _Generic. Eu não estou ciente de nenhum compilador onde isso funcionaria, mas a macro original não, mas ele o tira de um aviso de -Wpointer-arith:

#define IS_CONSTEXPR(expr) \
  __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)

Esta versão deve funcionar com o GCC de volta para 3.1, assim como com os compiladores que definem __GNUC__/__GNUC_MINOR__ com valores que indicam ≥ 3.1, como clang e ICC.

Macro desta resposta

Qualquer compilador que suporte sizeof(void) deve funcionar, mas há uma boa chance de você receber um aviso (como -Wpointer-arith). Dito isto, os compiladores AFAICT que do suportam sizeof(void) parecem sempre fazê-lo, portanto qualquer versão desses compiladores Deveria trabalhar:

  • GCC
  • Clang (e compiladores incorporados, que também definem __clang__)
  • ICC (testado 18.0)
  • XL C/C++ (testado 13.1.6)
  • TI (testado 8.0)
  • TinyCC

__builtin_constant_p

Dependendo do seu caso de uso, pode ser preferível usar __builtin_constant_p em compiladores que o suportam. É um pouco mais geral (e mais nebuloso) do que uma expressão constante inteira; apenas diz que o compilador conhece o valor em tempo de compilação. Esses compiladores são conhecidos por dar suporte a ele:

  • GCC 3.1+
  • Clang
  • ICC (testado 18.0)
  • TinyCC 0.9.19+
  • armcc 5.04+
  • XL C/C++ (não documentado, mas definitivamente funciona em 13.1.6 ou superior)

Se você estiver usando a macro para escolher entre um caminho de código que o compilador pode dobrar constantemente, se souber o valor no tempo de compilação, mas é lento no tempo de execução e um caminho de código que é uma caixa preta para o compilador, mas é rápido no tempo de execução, use __builtin_constant_p.

OTOH, se você quiser verificar se o valor é realmente um ICE de acordo com o padrão, não use __builtin_constant_p. Como exemplo, aqui está uma macro que retornará expr se o expr for um ICE, mas -1 se não for:

#if defined(ICE_P)
#  define REQUIRE_ICE(expr) (ICE_P(expr) ? (expr) : (-1))
#else
#  define REQUIRE_ICE(expr) (expr)
#endif

Você pode usar isso ao declarar uma matriz em uma macro se o compilador mostrar um erro se usar um VLA:

char foo[REQUIRE_ICE(bar)];

Dito isto, o GCC e o clang implementam um aviso -Wvla que você pode querer usar. A vantagem de -Wvla é que ele não requer modificações no código-fonte (ou seja, você pode simplesmente escrever char foo[bar];). As desvantagens são que ele não é tão amplamente suportado e o uso de parâmetros de matriz em conformidade também acionará o diagnóstico; portanto, se você deseja evitar muitos falsos positivos, essa macro pode ser sua melhor aposta.

Compiladores que não suportam nada

  • MSVC
  • DMC

Ideias bem-vindas :)

18
nemequ