C中的MIN和MAX

在C语言中,MINMAX定义在哪里?

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

1039589 次浏览

在c++中有std::minstd::max,但是在C标准库中没有等价的。你可以自己用宏来定义它们

#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))

但是如果你写类似MAX(++a, ++b)这样的东西,这会引起问题。

我不认为它们是标准化宏。浮点数已经有了标准化的函数,fmaxfmin(还有fmaxf用于浮点数,fmaxl用于长双精度浮点数)。

您可以将它们作为宏实现,只要您了解副作用/双重评估的问题。

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

在大多数情况下,你可以把它留给编译器来决定你要做什么,并尽可能优化它。虽然这在像MAX(i++, j++)这样使用时会导致问题,但我怀疑是否有必要一次性检查增量值的最大值。先递增,然后检查。

在C语言中,MINMAX定义在哪里?

他们不是。

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

作为功能。我不会使用#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))这样的宏,特别是如果你计划部署你的代码。你可以自己编写,使用标准的fmaxfmin,或者在GCC语句表达式中使用GCC的领域(你也可以获得类型安全奖励)修复宏:

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

每个人都说“哦,我知道双重求值,这没问题”,几个月后,你会连续几个小时调试最愚蠢的问题。

注意使用__typeof__而不是typeof:

如果你正在写一个头文件 必须工作时,包括在ISO C 程序,写入__typeof__而不是 typeof . < / p >

它也在sys/param.h的GNU libc (Linux)和FreeBSD版本中提供,并具有dreamlax提供的定义。


在Debian上:

$ uname -sr
Linux 2.6.11


$ cat /etc/debian_version
5.0.2


$ egrep 'MIN\(|MAX\(' /usr/include/sys/param.h
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))


$ head -n 2 /usr/include/sys/param.h | grep GNU
This file is part of the GNU C Library.

在FreeBSD上:

$ uname -sr
FreeBSD 5.5-STABLE


$ egrep 'MIN\(|MAX\(' /usr/include/sys/param.h
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

源存储库在这里:

如果你需要min/max来避免昂贵的分支,你不应该使用三元运算符,因为它会编译成一个跳转。下面的链接描述了实现最小/最大函数而不进行分支的有用方法。

< a href = " http://graphics.stanford.edu/ ~ seander / bithacks.html # IntegerMinOrMax " > http://graphics.stanford.edu/ ~ seander / bithacks.html # IntegerMinOrMax < / >

我写了这个版本,适用于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>)有maxmin(小写)宏,它们也遭受“双重求值”困难,但它们是为那些不想重新滚动自己的宏而存在的:)

我知道那家伙说“C”… 但如果有机会,请使用c++模板:

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

类型安全,其他注释中提到的++没有问题。

ab两个整数的最大值是(int)(0.5((a+b)+abs(a-b)))。这也可以用于双精度变量的(double)fabs(a-b)(类似于浮点数)

值得指出的是,我认为如果你用三元操作定义minmax,比如

#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)

避免使用非标准编译器扩展,在纯标准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基于type参数创建另一个宏。如果为给定类型实现了此控制宏,则用于检查两个参数的类型是否正确。如果不支持type,则会出现编译器错误。

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

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

然后,在c语言中存在隐式类型提升的常见问题。?:operator将第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))  \
)

问题是,如果上面的宏被调用为带有两个intMAX(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;
}

这是一个较晚的答案,由于最近的发展。由于OP接受了依赖于不可移植的GCC(和clang)扩展typeof或“干净”ISO C的__typeof__的答案,因此有一个更好的解决方案可用gcc - 4.9

#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })

这个扩展的明显好处是每个宏参数只展开一次,不像__typeof__解决方案。

__auto_type是c++ 11的auto的有限形式。它不能(或不应该?)在c++代码中使用,尽管在使用c++ 11时没有很好的理由不使用auto的高级类型推断功能。

也就是说,在假设中,当宏包含在extern "C" { ... }作用域中时,使用此语法没有问题;例如,来自C头文件。AFAIK,这个扩展没有找到它的方式信息叮当

最简单的方法是将它定义为.h文件中的全局函数,如果你的程序是包含大量文件的模块化的,那么你可以随时调用它。如果不是,double MIN(a,b){return (a<b?a:b)}是最简单的方法。

@David Titarenco说的很好,但至少让我清理一下,让它看起来更好,并同时显示min() 而且 max(),以使从这里复制和粘贴更容易。:)

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

1. 旧的C宏方式:

这个技巧被普遍使用,并且受到那些知道如何正确使用它的人的尊重,即“事实”。如果使用得当,可以使用,但是(想想:双重评估副作用)如果你曾经传入包含变量赋值的表达式来比较:

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

2. 新的和改进的gcc和clang "语句表达"方法:

这种技术避免了上述的“双重求值”;副作用和bug,因此被认为是更好,更安全,“更现代”;海湾合作委员会的方式来做这个。期望它能与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++模板的更多内容:维基百科:模板(c++)

然而,max()min()都已经是c++标准库的一部分,在<algorithm>头文件(#include <algorithm>)中。在c++标准库中,它们的定义与上面的略有不同。例如,在c++ 14中,std::max<>()std::min<>()的默认原型,在上面的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);

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

这里你可以看到输入类型和返回类型都是const T&,意思是“对类型__abc1的常量引用”。这意味着输入参数和返回值是通过引用而不是通过值传递。这类似于通过指针传递,并且对于大型类型(如类对象)更有效。修改函数本身和函数的constexpr部分表示函数必须能够在编译时被求值(至少在提供constexpr输入参数的情况下),但如果它不能在编译时求值,则它默认返回运行时求值,就像任何其他正常函数一样。

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

4. [c++专用]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);

...但如果你想一次比较多个数字,你可以使用4号形式,它接受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.

引用:

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

Clang note from Wikipedia:

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

相关:

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

旧的GCC扩展:操作符<?, >?, <?=, >?=

在一个非常旧的GCC版本中,有操作符<?, >?(参见在这里,这里是在c++中,但我认为它当时也应用为C扩展) 我还见过运算符<?=, >?=对应于赋值语句

操作数只计算一次,甚至允许使用非常短的赋值语句。与常见的最小/最大赋值相比,它非常短。没有什么比这更好的了。

这些是以下内容的简写:

min(a, b)   ===   a < b ? a : b   ===   a <? b;
max(a, b)   ===   a > b ? a : b   ===   a >? b;
a = min(a, b);   ===   if(b < a) a = b;   ===   a <?= b;
a = max(a, b);   ===   if(b > a) a = b;   ===   a >?= b;

求最小值非常简洁:

int find_min(const int* ints, int num_ints)
{
assert(num_ints > 0);
int min = ints[0];
for(int i = 1; i < num_ints; ++i)
min <?= ints[i];
return min;
}

我希望有一天GCC会用到这些运算符,因为我认为这些运算符很有天赋。

我将避免将MIN和MAX定义为预处理器宏。 即使使用避免双重求值的改进的实现,即

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

你会遇到其他问题,比如当你试图在函数之外使用它(参见在这里的例子)或这个答案中引用的变量阴影时,编译器错误。

如果你迫切需要宏所带来的性能优势,即代码内联,可以将它们实现为内联函数。大多数现代编译器都支持内联。比如像这样

inline int32_t MAX(int32_t a, int32_t b) { return((a) > (b) ? a : b); }
inline int32_t MIN(int32_t a, int32_t b) { return((a) < (b) ? a : b); }

(更多的在这里)

你必须为你需要的每一种数据类型实现一个变体,但通常你会使用有限的一组数据类型。话虽如此,请记住比较浮点数有它自己的怪癖,你需要比上面更复杂的代码(参见在这里进行讨论)。