Suponga que una función que debería ser portátil para los sistemas LP64 e ILP32, es decir, long int puede ser de 32 o 64 bits. Esta función tendrá alguna tabla de constantes, pero las constantes mismas deben basarse en el ancho del tipo. Un ejemplo artificial:

// Find largest power of 1000 less than x, aka log base 1000 rounded down to an integer
unsigned long int intlog1000l(unsigned long int x) {
    const unsigned long int powers[] = {
        0, 1000, 1000000, 1000000000,
        1000000000000, 1000000000000000, 1000000000000000000 };
    unsigned int i;
    for (i = 0; i < sizeof(powers)/sizeof(*powers); i++)
        if (powers[i] > x) break;
    return i - 1; 
}    

Si long int es de 64 bits, entonces este código funciona según lo previsto. Pero si long int es de 32 bits, fallará ya que las últimas tres constantes son demasiado grandes para caber.

Una forma de evitar esto sería cambiar la interfaz de la función y el tipo de tabla para que ambos utilicen uint32_t o uint64_t. Pero, ¿cómo se puede combinar esto con las API existentes que no usan esos tipos, como __builtin_clzl() o labs()?

Otra alternativa sería mantener la interfaz igual, pero promover el argumento dentro de la función al tamaño más grande que se admitirá, uint64_t, y guardar los elementos de la tabla en este tamaño. Pero eso es muy ineficiente en un sistema de 32 bits.

Se podría hacer arreglos para proporcionar una macro que defina el tamaño de un entero largo y luego colocar la segunda línea de la tabla dentro de un #if. Esto es difícil ya que sizeof() no está disponible para el preprocesador. Se necesita algo como autoconf para determinar el tamaño y generar un encabezado de configuración. Esto es difícil de encajar en un proceso de compilación existente.

En un intento de proporcionar el conjunto completo de funciones int, long int y long long int con / sin signo, esta es otra forma:

unsigned int intlog1000(unsigned int); // assume 32 bits
unsigned long long int intlog1000ll(unsigned long long int); // assume 64 bits
static inline unsigned long int intlog1000l(unsigned long int x)
{ sizeof(x) == sizeof(unsigned int) ? intlog1000(x) : intlog1000ll(x); }

Esto supone que será seguro asumir que int y long long int son de cierto tamaño, y que long int tendrá el mismo tamaño que uno u otro. Como este es el caso en casi todas las plataformas de 32 bits en la existencia actual.

¿Hay alguna forma mejor?

c c99
2
TrentP 4 abr. 2017 a las 00:33

2 respuestas

La mejor respuesta

Considere una serie de #if

#include <limits.h>
const unsigned long int powers[] = {
    0, 1000, 1000000, 1000000000
    #if ULONG_MAX/1000 >= 1000000000
      , 1000000000000u
    #endif
    #if ULONG_MAX/1000 >= 1000000000000
      , 1000000000000000u
    #endif
    #if ULONG_MAX/1000 >= 1000000000000000
      , 1000000000000000000u
    #endif
    };

Este enfoque anterior tiene problemas ya que "la matemática macro" a veces está firmada (en base a la experiencia, no a las especificaciones), por lo que el siguiente código asume razonablemente que el máximo unsigned long es aproximadamente 2x máximo signed long. el enfoque "anidado" es mejor ya que asegura que la "macro matemática" funcione, ya que depende del éxito anterior. Esto no es tan importante con los compiladores C99,11, ya que la matemática es de al menos 64 bits. Esto hace más diferencia con los compiladores antiguos o si uno quisiera extender este esquema a incluso más ancho que 64 bits unsigned long.

    #if LONG_MAX/1000 >= 500000000
      , 1000000000000u
      #if LONG_MAX/1000 >= 500000000000
        , 1000000000000000u
        #if LONG_MAX/1000 >= 5000000000000000
          , 1000000000000000000u
          #if LONG_MAX/1000 >= 5000000000000000000
            #error powers[] needs extending
          #endif
        #endif
      #endif
    #endif

"macro matemática" o mejor aritmética del preprocesador , se realiza con al menos matemática de 64 bits en C11 (y probablemente C99), pero solo con al menos 32 bits con versiones anteriores como C89.

Para los fines de esta conversión y evaluación de tokens, todos los tipos enteros con signo y todos los tipos enteros sin signo actúan como si tuvieran la misma representación que, respectivamente, los tipos intmax_t y uintmax_t definidos en el encabezado {{ X2}}.) Esto incluye la interpretación de constantes de caracteres, que pueden implicar la conversión de secuencias de escape en miembros del conjunto de caracteres de ejecución. C11 §6.10.1 4

3
chux - Reinstate Monica 3 abr. 2017 a las 22:08

Podría considerar dar a los elementos de la tabla el tipo unsigned long long int o uintmax_t (que puede o no ser lo mismo). Cualquier implementación que cumpla con C99 o C11 proporcionará un unsigned long long int que tenga al menos 64 bits de ancho y, por lo tanto, uintmax_t también tendrá al menos ese ancho.

Por supuesto, eso plantea la cuestión de cómo su función puede manejar entradas de más de 64 bits.

0
John Bollinger 3 abr. 2017 a las 22:00