C语言中的MIN和MAX定义在哪里?

实现这些最好的方法是什么,尽可能的泛型和类型安全?(主流编译器的编译器扩展/内置优先。)


当前回答

避免使用非标准编译器扩展,在纯标准C语言中实现完全类型安全的宏(ISO 9899:2011)。

解决方案

#define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))

#define ENSURE_int(i)   _Generic((i), int:   (i))
#define ENSURE_float(f) _Generic((f), float: (f))


#define MAX(type, x, y) \
  (type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))

使用

MAX(int, 2, 3)

解释

宏MAX基于类型参数创建另一个宏。如果为给定类型实现了此控制宏,则用于检查两个参数的类型是否正确。如果不支持该类型,则会出现编译器错误。

如果x或y的类型不正确,则ENSURE_宏中将出现编译器错误。如果支持更多类型,可以添加更多这样的宏。我假设只有算术类型(整数,浮点数,指针等)将被使用,而不是结构体或数组等。

如果所有类型都正确,将调用GENERIC_MAX宏。每个宏参数周围都需要额外的括号,这是编写C宏时通常的标准预防措施。

然后是c语言中隐式类型提升的常见问题。?:操作符平衡了第2和第3个操作数。例如,GENERIC_MAX(my_char1, my_char2)的结果将是一个int型。为了防止宏执行这种潜在危险的类型提升,使用了最终类型转换为预期类型。

基本原理

我们希望宏的两个参数具有相同的类型。如果其中一个是不同类型的,则宏不再是类型安全的,因为像?:这样的操作符将产生隐式类型提升。正因为如此,我们还总是需要将最终结果转换回上面解释的预期类型。

只有一个参数的宏可以用更简单的方式编写。但是对于2个或更多的参数,就需要包含一个额外的类型参数。因为不幸的是,这样的事情是不可能的:

// this won't work
#define MAX(x, y)                                  \
  _Generic((x),                                    \
           int: GENERIC_MAX(x, ENSURE_int(y))      \
           float: GENERIC_MAX(x, ENSURE_float(y))  \
          )

问题是,如果上面的宏被调用为带有两个int的MAX(1,2),它仍然会尝试宏展开_Generic关联列表的所有可能场景。因此ENSURE_float宏也会被展开,尽管它与int无关。由于该宏有意只包含float类型,因此代码无法编译。

为了解决这个问题,我在预处理器阶段使用##操作符创建了宏名,这样就不会意外地展开宏。

例子

#include <stdio.h>

#define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))

#define ENSURE_int(i)   _Generic((i), int:   (i))
#define ENSURE_float(f) _Generic((f), float: (f))


#define MAX(type, x, y) \
  (type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))

int main (void)
{
  int    ia = 1,    ib = 2;
  float  fa = 3.0f, fb = 4.0f;
  double da = 5.0,  db = 6.0;

  printf("%d\n", MAX(int,   ia, ib)); // ok
  printf("%f\n", MAX(float, fa, fb)); // ok

//printf("%d\n", MAX(int,   ia, fa));  compiler error, one of the types is wrong
//printf("%f\n", MAX(float, fa, ib));  compiler error, one of the types is wrong
//printf("%f\n", MAX(double, fa, fb)); compiler error, the specified type is wrong
//printf("%f\n", MAX(float, da, db));  compiler error, one of the types is wrong

//printf("%d\n", MAX(unsigned int, ia, ib)); // wont get away with this either
//printf("%d\n", MAX(int32_t, ia, ib)); // wont get away with this either
  return 0;
}

其他回答

我编写的这个版本适用于MSVC、GCC、C和c++。

#if defined(__cplusplus) && !defined(__GNUC__)
#   include <algorithm>
#   define MIN std::min
#   define MAX std::max
//#   define TMIN(T, a, b) std::min<T>(a, b)
//#   define TMAX(T, a, b) std::max<T>(a, b)
#else
#       define _CHOOSE2(binoper, lexpr, lvar, rexpr, rvar) \
                ({ \
                        decltype(lexpr) lvar = (lexpr); \
                        decltype(rexpr) rvar = (rexpr); \
                        lvar binoper rvar ? lvar : rvar; \
                })
#       define _CHOOSE_VAR2(prefix, unique) prefix##unique
#       define _CHOOSE_VAR(prefix, unique) _CHOOSE_VAR2(prefix, unique)
#       define _CHOOSE(binoper, lexpr, rexpr) \
                _CHOOSE2( \
                        binoper, \
                        lexpr, _CHOOSE_VAR(_left, __COUNTER__), \
                        rexpr, _CHOOSE_VAR(_right, __COUNTER__) \
                )
#       define MIN(a, b) _CHOOSE(<, a, b)
#       define MAX(a, b) _CHOOSE(>, a, b)
#endif

看起来像Windef.h (a la #include <windows.h>)有max和min宏(小写),这也遭受了“双重求值”的困难,但它们是为那些不想重新滚动自己的:)

@David Titarenco在这里写得很好,但至少让我把它清理一下,让它看起来更漂亮,并同时显示min()和max(),以便从这里复制和粘贴。:)

2020年4月25日更新:我还增加了第3节,以展示如何使用c++模板来实现这一点,对于那些同时学习C和c++,或从一个过渡到另一个的人来说,这是一个有价值的比较。我已经尽我所能做到全面、真实和正确,使这个答案成为我可以反复使用的权威参考,我希望你能和我一样觉得它有用。

1. 旧的C宏方式:

这种技术是常用的,受到那些知道如何正确使用它的人的尊重,这是一种“事实上”的做事方式,如果使用得当,使用起来很好,但如果你传递包含变量赋值的表达式来进行比较,就会出现bug(想想:双重求值的副作用):

#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))

2. 改进后的gcc和clang“语句表达式”方式:

这种技术避免了上述“双重计算”的副作用和错误,因此被认为是更好、更安全、“更现代”的GCC C方法。期望它能与gcc和clang编译器一起工作,因为clang在设计上是与gcc兼容的(请参阅答案底部的clang注释)。

但是:仍然要注意“变量阴影”效果,因为语句表达式显然是内联的,因此没有自己的局部变量作用域!

#define max(a,b)             \
({                           \
    __typeof__ (a) _a = (a); \
    __typeof__ (b) _b = (b); \
    _a > _b ? _a : _b;       \
})

#define min(a,b)             \
({                           \
    __typeof__ (a) _a = (a); \
    __typeof__ (b) _b = (b); \
    _a < _b ? _a : _b;       \
})

注意,在gcc语句表达式中,代码块中的最后一个表达式是表达式“返回”的表达式,就像从函数返回一样。GCC的文档是这样说的:

复合语句的最后一个东西应该是一个带分号的表达式;这个子表达式的值作为整个构造的值。(如果在大括号内使用其他类型的语句,则构造的类型为void,因此实际上没有值。)

3.[c++专用]c++模板方式:

注意:如果使用c++,这种类型的构造可能推荐使用模板,但我个人不喜欢模板,可能会在c++中使用上述构造之一,因为我经常在嵌入式c++中使用并更喜欢C样式。

新增2020年4月25日:

在过去的几个月里,我一直在做大量的c++,在c++社区中,更喜欢模板而不是宏的压力是相当大的。因此,我在使用模板方面做得越来越好,为了完整起见,我想在这里加入c++模板版本,使它成为一个更规范、更全面的答案。

下面是c++中max()和min()的基本函数模板版本:

template <typename T>
T max(T a, T b)
{
    return a > b ? a : b;
}

template <typename T>
T min(T a, T b)
{
    return a < b ? a : b;
}

在这里阅读有关c++模板的更多信息:Wikipedia: Template (c++)。

然而,max()和min()已经是c++标准库的一部分,在<algorithm>标头中(#include <algorithm>)。在c++标准库中,它们的定义与上面的略有不同。std::max<>()和std::min<>()的默认原型,例如,在c++ 14中,在上面的cplusplus.com链接中查看它们的原型是:

template <class T> 
constexpr const T& max(const T& a, const T& b);

template <class T> 
constexpr const T& min(const T& a, const T& b);

注意,关键字typename是类的别名(所以无论你说<typename T>还是<class T>,它们的用法都是相同的),因为在c++模板发明之后,模板类型可能是常规类型(int, float等),而不仅仅是类类型。

Here you can see that both of the input types, as well as the return type, are const T&, which means "constant reference to type T". This means the input parameters and return value are passed by reference instead of passed by value. This is like passing by pointers, and is more efficient for large types, such as class objects. The constexpr part of the function modifies the function itself and indicates that the function must be capable of being evaluated at compile-time (at least if provided constexpr input parameters), but if it cannot be evaluated at compile-time, then it defaults back to a run-time evaluation, like any other normal function.

constexpr c++函数的编译时特性使它有点像C-宏,因为如果constexpr函数可以在编译时求值,那么它将在编译时完成,就像在C或c++中MIN()或MAX()宏替换也可能在编译时完全求值一样。有关此c++模板信息的其他参考资料,请参见下面。

4. [c++ only] c++ std::max()

如果使用c++,我想在<algorithm>头文件中添加内置的std::max()函数具有多种形式。请参阅cppreference.com社区wiki (https://en.cppreference.com/w/cpp/algorithm/max)文档页面上的“可能实现”部分,了解std::max()的4种形式的4种可能实现。

正常用法包括:

std::max(100, 200);

...但如果你想一次比较多个数字,你可以使用第四种形式,它接受std::initializer_list<T>,就像这样:

函数声明:

template< class T, class Compare >
constexpr T max( std::initializer_list<T> ilist, Compare comp );

用法:

// Compare **3 or more numbers** by passing a curly-brace-initialized
// `std::initializer_list<>` to `std::max()`!:

std::max({100, 200, 300});                  // result is 300
std::max({100, 200, 300, 400});             // result is 400
std::max({100, 200, 300, 400, 500});        // result is 500
std::max({100, 200, 300, 400, 500, 600});   // result is 600
// etc.

引用:

https://gcc.gnu.org/onlinedocs/gcc/Typeof.html#Typeof https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs C中的MIN和MAX 2020年4月新增c++模板参考: *****维基百科:模板(c++) <——伟大的额外信息关于c++模板! (我自己的问题和回答):为什么' constexpr '是' std::max() '的c++ 14模板原型的一部分? constexpr和const的区别是什么?

来自维基百科的提示:

[Clang]被设计为GNU编译器集合(GCC)的临时替代品,支持其大多数编译标志和非官方语言扩展。

相关:

[我的答案]舍入整数除法(而不是截断)——这里我也使用宏、gcc/clang语句表达式和c++模板。

两个整数a和b的最大值是(int)(0.5((a+b)+abs(a-b)))。这也可以用于(double)和fabs(a-b)用于double(类似于float)

值得指出的是,如果你用三元运算来定义最小值和最大值,比如

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

然后,为了在fmin(-0.0,0.0)和fmax(-0.0,0.0)的特殊情况下获得相同的结果,您需要交换参数

fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)