C:寻找算术表达式类型的最大值和最小值

6
我需要找到任意 C 表达式的最大值和最小值,该表达式没有副作用。以下宏在我的机器上工作。它们会在所有平台上工作吗?如果不行,能不能修改使之正常工作?我的目的是随后使用这些宏来实现类似于 SAFE_MUL(a,b) 的宏以替代 a*bSAFE_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;
}

很好的问题!不过,我会用自身的异或替换乘以零;这是许多嵌入式编译器不会执行的优化。 - Mahmoud Al-Qudsi
@mahmoud:我不确定我是否理解了这个评论。我希望在示例中的所有宏都在编译时评估。因此,XOR导致这些宏翻译成任何常量以上的内容实际上是不可取的。换句话说,(exp)*0 的目的是获得一个 0,将其转换为 (exp) 的类型,但要求 exp 的类型不小于 int。 - tyty
@user1016492,你的问题似乎比示例更为普遍,因为你谈到了“没有副作用的任意C表达式”,这不一定是编译时常量;此外,如果ab不是常量,SAFE_MUL(a,b)似乎更有用。 - anatolyg
@user1016492 我明白了。我认为使用exp^exp可以达到同样的效果。 - Mahmoud Al-Qudsi
以@anatolyg为例,假设有一个表达式exp1:(x+1) * y * z / (x + y),其中x、y、z本身可能是任意表达式。我期望优化编译器能够在编译时评估TYPE_MAX(exp1),而不管xyz的值如何。话虽如此,我同意这个问题比示例所能处理的要更普遍,原因有3个(由steve指出):(1)由无符号常量组成的表达式小于int将无法工作。 (2)涉及浮点数和双精度的表达式将无法工作。 (3)TYPE_MAX返回的值可能小于实际值。 - tyty
@anatolyg:我忘记在下面你的回答中提到了(2)。 - tyty
2个回答

3
  • IS_SIGNED宏对于小于int的无符号类型不准确。在任何正常实现中,IS_SIGNED((unsigned char)1)都是真的,因为(unsigned char)1*0的类型是int,而不是unsigned char

你最终的SAFE宏应该仍然能够准确地告诉你是否发生了溢出,因为所有算术运算都适用于相同的整数提升。但是,它们将告诉您是否在乘法中发生了溢出,而不一定告诉您当用户将结果转换回操作数的原始类型时是否会发生溢出。

想想看,你可能已经知道了,因为你的宏没有尝试建议CHAR_MIN等。但是,将来找到这个问题的其他人可能没有意识到这个限制。

  • 没有单个类型可以保证能够容纳所有TYPE_MINTYPE_MAX可以计算的值。但是,您可以使TYPE_MAX始终计算为unsigned long long(并且该值始终适合该类型),并且与TYPE_MINsigned long long相同。这将允许您使用正确的printf格式,而不必使用有关表达式是否带符号的知识。目前,TYPE_MAX(1)是一个long long,而TYPE_MAX(1ULL)是一个unsigned long long

  • 从技术上讲,intlong具有相同的大小但不同的范围是允许的,因为longint少有填充位。不过,我怀疑任何重要的实现都没有这样做。


我真的很想知道为什么C语言(和C++)被定义得如此宽松。这使得开发变得更加困难。 - orlp
@steve:谢谢。填充位的问题是一个关注点,因为无法从sizeof(exp)中确定它是int还是long。因此,我们只能依靠提升规则来获得更严格的范围,这足以满足我的目的。 - tyty
@nightcracker 因为反过来,这使得在嵌入式设备和平台上实现它变得更加容易。 - Mahmoud Al-Qudsi
@nightcracker: 是的,我同意。编写可移植代码非常麻烦,而且很容易出错。我想知道是否有任何开源代码真正能够在标准的环境下实现可移植性。 - tyty
@steve:除了 int 类型之外的任何类型(例如 short、char、long 和 long long)是否有填充位没有任何保证? - tyty
@user1016492:只有字符类型(charsigned charunsigned char)保证没有填充位,加上所有固定宽度类型((u)intN_t)如果存在,则保证没有填充位。 - Steve Jessop

1

一个想法:如果你使用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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接