C中的MIN和MAX


299

在哪里MINMAX在C,规定如果在所有?

一般地并安全地键入这些内容的最佳方法是什么?(首选主流编译器的编译器扩展/内置插件。)

Answers:


392

在哪里MINMAX在C,规定如果在所有?

他们不是。

最好的方式是实现它们,并且尽可能地安全键入(最好是主流编译器使用的编译器扩展/内置插件)。

作为功​​能。我不会使用像这样的宏#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)),特别是如果您打算部署代码。在GCC语句表达式中编写您自己的代码,使用诸如standard fmax或之类的东西fmin,或者使用GCC的typeof修复宏(您也可以获得类型安全性奖励):

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

每个人都说:“哦,我知道双重评估,这没问题。”几个月之后,您将连续数小时调试最严重的问题。

注意使用__typeof__代替typeof

如果要编写的标头文件包含在ISO C程序中时必须起作用,请写__typeof__而不是 typeof


68
您知道吗,如果gcc发出以下警告,则非常方便:warning: expression with side-effects multiply evaluated by macro在使用时...
caf 2010年

23
@caf:是否要求预处理器具有更复杂的C语法知识?
dreamlax 2010年

3
经过大量尝试后,我认为在VC ++中还是无法执行此操作,但是最好的方法是尝试弄乱MSVC ++ 2010 new decltype关键字-但是即使如此,Visual Studio也无法在宏中执行复合语句(decltype无论如何都是C ++),即GCC的({ ... })语法,所以我很确定这是不可能的。对于这个问题,我还没有看过其他编译器,对不起路德:S
David Titarenco,2010年

7
@dreamlax我曾经见过有人MAX(someUpperBound, someRandomFunction())将随机值限制为某个上限的情况。这是一个糟糕的主意,但它甚至也没有用,因为MAX他使用的是双重评估问题,因此他得到的随机数与最初评估的随机数不同。
Zev Eisenberg 2014年

8
@Soumen例如,如果您调用MIN(x++, y++)预处理器,则会生成以下代码(((x++) < (y++)) ? (x++) : (y++))。所以,xy将两次递增。
安东尼奥

91

它还在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))

源代码库在这里:


我在上面的答案中添加了我可以访问的系统的定义(就我所知,注释字段不接受格式设置)。将尝试找到指向FreeBSD / Linux / glibc源存储库的链接。
Mikel 2010年

+1。非常好。也适用于openSUSE/Linux 3.1.0-1.2-desktop/ gcc version 4.6.2 (SUSE Linux) 。:)不好,它不能移植。
2012年

也适用于Cygwin。
CMCDragonkai '17

1
等一会。它不会阻止双重评估,对吗?:3
user1857492 '17

76

在C ++中有一个std::minstd::max,但是AFAIK在C标准库中没有等效项。您可以自己定义宏,例如

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

但这会导致问题,如果您编写类似的内容MAX(++a, ++b)


10
为什么要放太多括号???我找到了一个测验,他们说#define MIN(A, B) ((A < B) ? A : B)这不是灵活的方法,为什么?

78
@Makouda:宏中的多余括号有助于避免运算符优先级问题。例如,考虑#define MULT(x, y) x * y。然后MULT(a + b, a + b)扩展为a + b * a + ba + (b * a) + b由于优先级解析为。那不是程序员可能想要的。
dan04 2014年

在?:优先级最低时不需要
Winger Sendon '18

@WingerSendon:没有;逗号运算符。
dan04 '19

24

避免使用非标准的编译器扩展,并在纯标准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中存在隐式类型提升的常见问题。?:运算符使第二和第三操作数彼此平衡。例如,的结果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))  \
          )

问题是,如果上述宏MAX(1, 2)与2 一样被调用int,它将仍然尝试对_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;
}

GENERIC_MAX顺便说一句,那个宏是一个坏主意,您只需要尝试GENERIC_MAX(var++, 7)找出原因:-)如今(尤其是在大量优化/内联的编译器中),宏应该只被简化为简单形式。类似函数的函数最好作为函数,而值组的函数最好作为枚举。
paxdiablo

21

我认为它们不是标准化的宏。已经有针对浮点fmaxfmin(以及fmaxf针对浮点和fmaxl长双精度)的标准化函数。

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

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

在大多数情况下,您可以将其留给编译器来确定要执行的操作,并尽可能地对其进行优化。尽管这在使用时会引起问题MAX(i++, j++),但我怀疑是否一过便需要检查增量值的最大值。首先增加,然后检查。


这应该是首选答案,因为数学库中显然有min和max函数:cplusplus.com/reference/cmath/fmax
imranal

@imranal您到底在说什么?那些库的实现代码?但是该代码没有公开,也就是说,它们没有将其放置在库的接口中,这可能是不安全的。
安东尼奥

@Antonio我认为您使用的是“暴露”和“接口”的错误定义。ac库的接口是头文件中的外部变量,类型,宏和函数声明。fmin / fmax在头文件中声明,因此它们被公开。我不确定您所说的是不安全的。
Rationalcoder

21

由于最近的发展,这是一个较晚的答案。由于OP接受了基于非便携式GCC(和clang)扩展名的答案typeof-或__typeof__“干净的” ISO C-从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。尽管没有充分的理由不auto使用C ++ 11时的高级类型推断功能,但不能(或不应该?)在C ++代码中使用它。

也就是说,当宏包含在作用域中时,我认为使用此语法没有问题extern "C" { ... }。例如,从C标头开始。AFAIK,此扩展程序未找到信息提示声


Brett Hale的评论有关clang__auto_type2016年左右开始提供支持(请参阅patch)。
拉尔斯

认识到宏问题的荣誉,但我仍然认为函数可能会更好:-)
paxdiablo

@paxdiablo-我同意,尽管问题带有c-preprocessor标签。除非使用gcc的__always_inline__属性,否则即使使用所述关键字也不能保证内联函数。
布雷特·黑尔

11

我写了这个版本,对于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

我进行了投票,但保留了以下划线开头,大写字母开头的标识符。
dreamlax '16

8

如果您需要最小/最大以避免一个昂贵的分支,则不应该使用三元运算符,因为它会编译为跳转。下面的链接描述了一种无需分支即可实现最小/最大函数的有用方法。

http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax


1
如果编译器足够聪明,则可以避免使用该分支
Axel Gneiting 2011年

2
如果打开了优化功能,则大多数情况下,所有现代编译器都会发出条件移动,而不是分支,因此使用这种hacks毫无意义。
KrzysztofKosiński2015年

1
绝对正确,我不知道那时我在看什么,已经有一段时间了。在x86和armv7a上,gcc和clang都避免使用-O分支。
cib 2015年

6

@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“ 语句表达 ”方式:

此技术避免了上述“双重评估”的副作用和错误,因此被认为是实现此目的的优越,安全和“更现代”的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 ++模板版本以确保完整性,并使之成为更规范,更彻底的答案。

这里的基本是什么函数模板的版本max(),并min()可能看起来像在C ++:

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 ++标准库中,它们的定义与上面我所定义的略有不同。例如,在C ++ 14中,std::max<>()and 的默认原型是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);

请注意,关键字typename是一个别名class(所以它们的用法是相同的,你是否说<typename T><class T>),因为它的C ++模板的发明后来经过确认,该模板类型可能是一个普通类型(intfloat,等),而不是只类类型。

在这里,您可以看到两种输入类型以及返回类型均为const T&,这意味着“对类型的恒定引用T”。这意味着输入参数和返回值是通过引用传递的,而不是通过value传递的。这就像通过指针传递一样,并且对于大型类型(例如类对象)更有效。constexpr函数的一部分修改了函数本身,并指示该函数必须能够在编译时进行评估(至少在提供constexpr输入参数的情况下),但是如果无法在编译时进行评估,则默认返回到像任何其他正常功能一样,运行时评估。

constexprC ++函数的编译时特性使其类似于C宏,因为如果可以对constexpr函数进行编译时求值,则将在编译时完成,就像可能进行a MIN()MAX()宏替换一样也可以在编译时使用C或C ++进行全面评估。有关此C ++模板信息的其他参考,请参见下文。

参考文献:

  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 ++模板参考:
    1. ***** 维基百科:模板(C ++) <-关于C ++模板的更多信息!
    2. (我自己的问题和答案):为什么“ constexpr”是std :: max()的C ++ 14模板原型的一部分?
    3. `constexpr`和`const`之间的区别

维基百科的 lang语:

[Clang]旨在替代GNU编译器集合(GCC),支持其大多数编译标志和非官方语言扩展。


对于过去24小时来的投票者:好消息!我删除了昨天在第3节中添加的第4节rant,并将其放在此处。我们欢迎您重新评估我的答案,并根据需要给它一个评价,因为我已经在其中添加了很多有用的信息,并已尽我所能使它成为一个坚实,有用,规范的答案,使所有人受益。现在又回到了重点。:) 谢谢!
Gabriel Staples

4

值得指出的是,我认为如果您定义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)

仍然不适用于NaN。fmin(3.0,NaN)==fmin(NaN,3.0)==fmax(3.0,NaN)==fmax(NaN,3.0)==3.0
greggo

@greggo,我在这里给出了更好的答案stackoverflow.com/a/30915238/2542702
Z玻色子

4

看起来Windef.h(a la #include <windows.h>)具有maxmin(小写)宏,它们也遭受“双重评估”的困难,但是对于那些不想重新滚动自己的宏来说,它们就在那里:)


12
你甚至感到惊讶吗?
马特·乔纳

2

我知道那个人说“ C” ...但是如果有机会,请使用C ++模板:

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

键入safe,其他注释中提到的++都没有问题。


16
参数应该是const引用,您永远不知道将通过什么用户。
nmikhailov 2013年

6
这样的功能已经标准化(std :: min)。
dreamlax

对于大多数正常用途,C ++具有很多标准功能,请不要重蹈覆辙。但是,MS还定义了自己的最小值/最大值,有时这可能会引起问题
phuclv

0

两个整数的最大ab(int)(0.5((a+b)+abs(a-b)))。这可能也适用于双打(double)fabs(a-b)双打(对于浮点数类似)


抱歉,如果这是错误的,我是C初学者,但是这段代码对我
NRZ

2
我不确定它是否适用于非整数。浮点数学具有非线性精度。
Treesrule14

扩展@ Treesrule14的评论:这不起作用,因为计算机对待数字的方式与数学家不同。浮点数具有舍入问题,因此您不太可能获得正确的答案。即使您使用整数数学运算,MAX_INT + MAX_INT也会给出-2,因此使用您的公式的max(MAX_INT,MAX_INT)将显示为-1。
user9876 2014年

-3

最简单的方法是将其定义为.h文件中的全局函数,并在程序包含大量文件的情况下随时调用它。如果不是,那double MIN(a,b){return (a<b?a:b)}是最简单的方法。


1
@technosaurus,如果您描述了此解决方案错误的原因,而不仅仅是它是错的,那将会很有帮助。
Tur1ng

@technosaurus,您的回复确实无济于事。如图1所示,似乎该函数定义完全错误(输入参数缺少类型,返回语句后缺少分号),并且将int输入转换为double是一种不好的处理方式,因此类型不应为double。在这里定义或语句表达式会更好(例如:参见此处),但如果是一个函数,请考虑为int32_t类型创建一个函数,为uint32_t类型创建一个函数,为float或double类型创建一个函数,总共3个不同的功能。
加布里埃尔·斯台普斯

1
@GabrielStaples此答案应标记为不是答案-没有帮助。尽管它可以用作在最小的空间中如何成为最错误的示例。推荐在标头中使用全局函数(不是静态内联的吗?)会破坏带有2个以上编译单元的代码,甚至不会编译,将函数命名为宏,隐含ints类似于1989,则无缘无故地返回double,这意味着强制转换将最多发出警告……而且最重要的是,它不会回答问题-不是通用的,不是类型安全的,而且绝对不是最佳方法
-technosaurus

这些问题中的每一个都应受到进一步的批评,这些批评不能得到足够详细的介绍。
Technosaurus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.