C: нахождение максимума и минимума типа арифметического выражения - PullRequest
6 голосов
/ 31 октября 2011

Мне нужно найти максимум и минимум произвольного выражения C, которое не имеет побочных эффектов. Следующие макросы работают на моей машине. Будут ли они работать на всех платформах? Если нет, могут ли они быть изменены для работы? Я собираюсь использовать их для реализации макросов типа SAFE_MUL(a,b) вместо a*b. SAFE_MUL будет включать проверку на переполнение умножения.

РЕДАКТИРОВАТЬ: тип приводится в соответствии с предложением Стива.

#include <stdio.h>
#include <limits.h>

#define IS_SIGNED(exp) (((exp)*0-1) < 0)

#define TYPE_MAX_UNSIGNED(exp) ((exp)*0-1)

#define TYPE_MAX_SIGNED(exp) ( \
    sizeof (exp) == sizeof (int) \
    ? \
    INT_MAX \
    : \
    ( \
        sizeof (exp) == sizeof (long) \
        ? \
        LONG_MAX \
        : \
        LLONG_MAX \
    ) \
)

#define TYPE_MAX(exp) ((unsigned long long)( \
    IS_SIGNED (exp) \
    ? \
    TYPE_MAX_SIGNED (exp) \
    : \
    TYPE_MAX_UNSIGNED (exp) \
))

#define TYPE_MIN_SIGNED(exp) ( \
    sizeof (exp) == sizeof (int) \
    ? \
    INT_MIN \
    : \
    ( \
        sizeof (exp) == sizeof (long) \
        ? \
        LONG_MIN \
        : \
        LLONG_MIN \
    ) \
)

#define TYPE_MIN(exp) ((long long)( \
    IS_SIGNED (exp) \
    ? \
    TYPE_MIN_SIGNED (exp) \
    : \
    (exp)*0 \
))

int
main (void) {

    printf ("TYPE_MAX (1 + 1) = %lld\n", TYPE_MAX (1 + 1));
    printf ("TYPE_MAX (1 + 1L) = %lld\n", TYPE_MAX (1 + 1L));
    printf ("TYPE_MAX (1 + 1LL) = %lld\n", TYPE_MAX (1 + 1LL));
    printf ("TYPE_MAX (1 + 1U) = %llu\n", TYPE_MAX (1 + 1U));
    printf ("TYPE_MAX (1 + 1UL) = %llu\n", TYPE_MAX (1 + 1UL));
    printf ("TYPE_MAX (1 + 1ULL) = %llu\n", TYPE_MAX (1 + 1ULL));
    printf ("TYPE_MIN (1 + 1) = %lld\n", TYPE_MIN (1 + 1));
    printf ("TYPE_MIN (1 + 1L) = %lld\n", TYPE_MIN (1 + 1L));
    printf ("TYPE_MIN (1 + 1LL) = %lld\n", TYPE_MIN (1 + 1LL));
    printf ("TYPE_MIN (1 + 1U) = %llu\n", TYPE_MIN (1 + 1U));
    printf ("TYPE_MIN (1 + 1UL) = %llu\n", TYPE_MIN (1 + 1UL));
    printf ("TYPE_MIN (1 + 1ULL) = %llu\n", TYPE_MIN (1 + 1ULL));
    return 0;
}

Ответы [ 2 ]

3 голосов
/ 31 октября 2011
  • Макрос IS_SIGNED не говорит правду с неподписанными типами, меньшими чем int. IS_SIGNED((unsigned char)1) верно для любой обычной реализации, потому что тип (unsigned char)1*0 равен int, а не unsigned char.

Ваши возможные макросы SAFE должны по-прежнему говорить правду о том, происходит ли переполнение, поскольку одни и те же целочисленные повышения применяются ко всей арифметике. Но они скажут вам, происходит ли переполнение при умножении, , а не , обязательно, происходит ли это, когда пользователь преобразует результат обратно в исходный тип одного из операндов.

Если подумать, вы, наверное, уже знали об этом, поскольку ваши макросы не пытаются предложить CHAR_MIN и так далее. Но другие люди, которые найдут этот вопрос в будущем, могут не осознавать этого ограничения.

  • Не существует единого типа, который гарантированно мог бы содержать все значения, которые могут вычислять TYPE_MIN и TYPE_MAX. Но вы можете сделать так, чтобы TYPE_MAX всегда оценивалось как unsigned long long (и значение всегда соответствует этому типу), и то же самое с TYPE_MIN и signed long long. Это позволит вам использовать правильный формат printf, не зная, подписано ли выражение. В настоящее время TYPE_MAX(1) является long long, тогда как TYPE_MAX(1ULL) является unsigned long long.

  • Технически разрешено, чтобы int и long имели одинаковый размер, но разные диапазоны, поскольку long имеет меньше битов заполнения, чем int. Я сомневаюсь, что любая важная реализация делает это, хотя.

1 голос
/ 31 октября 2011

Просто идея: если вы используете gcc, вы можете использовать расширение typeof:

#define IS_SIGNED(exp) ((typeof(exp))-1 < 0)
#define TYPE_MAX_UNSIGNED(exp) ((typeof(exp))-1)
#define TYPE_MAX_SIGNED(exp) ... // i cannot improve your code here

Редактировать: может также проверять типы с плавающей точкой:

#define CHECK_INT(exp) ((typeof(exp))1 / 2 == 0)
#define CHECK_INT(exp) (((exp) * 0 + 1) / 2 == 0) // if cannot use typeof
#define MY_CONST_1(exp) (1/CHECK_INT(exp))
// Now replace any 1 in code by MY_CONST_1(exp) to cause error for floating-point
...