C中的最小值和最大值
Posted
技术标签:
【中文标题】C中的最小值和最大值【英文标题】:MIN and MAX in C 【发布时间】:2011-03-27 03:06:08 【问题描述】:如果有的话,MIN
和 MAX
在 C 中定义在哪里?
实现这些的最佳方法是什么,尽可能通用并尽可能安全地输入? (首选主流编译器的编译器扩展/内置。)
【问题讨论】:
有人可以检查this 并判断它是宏还是函数?我的意思是,在min(x++, y++)
行中,如果我使用这个min
,x 和y 会增加一到两次。
【参考方案1】:
如果有的话,
MIN
和MAX
在 C 中定义在哪里?
他们不是。
实现这些的最佳方法是什么,尽可能通用且类型安全(首选主流编译器的编译器扩展/内置)。
作为函数。我不会使用像#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
这样的宏,特别是如果你打算部署你的代码。要么自己编写,要么使用标准 fmax
或 fmin
之类的东西,或者在 GCC statement expression 中使用 GCC's typeof 修复宏(你也可以获得类型安全奖励):
#define max(a,b) \
( __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; )
每个人都说“哦,我知道双重评估,这没问题”,几个月后,您将连续数小时调试最愚蠢的问题。
注意使用__typeof__
而不是typeof
:
如果你正在编写一个头文件 包含在 ISO C 中时必须工作 程序,写
__typeof__
而不是typeof
.
【讨论】:
你知道,如果 gcc 在使用时有类似以下内容的警告会非常方便:warning: expression with side-effects multiply evaluated by macro
...
@caf:那不是要求预处理器有更复杂的 C 语法知识吗?
经过多次尝试,我认为在 VC++ 中没有办法做到这一点,但最好的办法是尝试使用 MSVC++ 2010 新的 decltype
关键字——但即便如此,Visual Studio 不能在宏中执行复合语句(而且decltype
无论如何都是C++),即GCC 的( ... )
语法,所以我很确定这是不可能的。我没有看过任何其他关于这个问题的编译器,对不起 Luther :S
@dreamlax 我曾经看到一个案例,有人使用MAX(someUpperBound, someRandomFunction())
将随机值限制在某个上限。这是一个糟糕的想法,但它甚至没有用,因为他使用的 MAX
存在双重评估问题,所以他最终得到的随机数与最初评估的随机数不同。
@Soumen 例如,如果您调用MIN(x++, y++)
,预处理器将生成以下代码(((x++) < (y++)) ? (x++) : (y++))
。因此,x
和 y
将增加两次。【参考方案2】:
GNU libc (Linux) 和 FreeBSD 版本的sys/param.h
也提供了它,并且具有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))
源代码库在这里:
GNU C Library FreeBSD【讨论】:
我在上面的答案中添加了我可以访问的系统的定义(据我所知,评论字段不接受格式)。将尝试找到 FreeBSD/Linux/glibc 源代码库的链接。 +1。很不错。也适用于openSUSE/Linux 3.1.0-1.2-desktop
/gcc version 4.6.2 (SUSE Linux)
。 :) 不好,它不便携。
也适用于 Cygwin。
等一下。它不会阻止双重评估,不是吗? :3【参考方案3】:
C++ 中有std::min
和std::max
,但据我所知,C 标准库中没有等价物。您可以使用宏来自己定义它们,例如
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
但是如果你写像MAX(++a, ++b)
这样的东西,这会导致问题。
【讨论】:
为什么要放太多括号???我发现了一个测验,他们说#define MIN(A, B) ((A < B) ? A : B)
不是一种灵活的方式,为什么???
@Makouda:宏中的额外括号有助于避免运算符优先级问题。例如,考虑#define MULT(x, y) x * y
。然后MULT(a + b, a + b)
扩展为a + b * a + b
,由于优先级,它解析为a + (b * a) + b
。这可能不是程序员的本意。
当 ?: 的优先级最低时不需要
@WingerSendon:它没有;逗号操作符。
但是你不能将带有逗号运算符的表达式作为参数传递给宏,除非你把它括起来【参考方案4】:
避免使用非标准编译器扩展并将其实现为纯标准 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 使第二个和第三个操作数相互平衡。例如,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)
和两个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)
即可找出原因 :-) 如今(尤其是在高度优化/内联编译器的情况下),宏应该很漂亮很多都只能归结为简单的形式。类函数更适合作为函数,值组更适合作为枚举。【参考方案5】:
由于最近的发展,这是一个较晚的答案。由于 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
的有限形式。它不能(或不应该?)在 C++ 代码中使用,尽管在使用 C++11 时没有充分的理由不使用 auto
的卓越类型推断功能。
也就是说,我假设当宏包含在extern "C" ...
范围内时,使用此语法没有问题;例如,来自 C 标头。 AFAIK,这个扩展程序没有找到它的方式信息铿锵声
【讨论】:
与Brett Hale's comment相关,clang
在2016年左右开始支持__auto_type
(见patch)。
感谢您认识到宏观问题,但我仍然认为函数可能会更好:-)
@paxdiablo - 我同意,虽然这个问题有 c-preprocessor
标签。即使使用上述关键字,也不能保证内联函数,除非使用 gcc 的 __always_inline__
属性。
这仍然使用 GCC(和 clang)( ... )
扩展。我不认为它比带有typeof
(带或不带下划线)的版本更便携。【参考方案6】:
我不认为它们是标准化的宏。已经有用于浮点的标准化函数,fmax
和 fmin
(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 你到底在说什么?那些库的实现代码?但是该代码没有公开,即他们没有将其放置在库的接口中,这可能是不安全的。 @Antonio 我认为您对“暴露”和“界面”的定义不正确。 c 库的接口是头文件中的外部变量、类型、宏和函数声明; fmin/fmax 在头文件中声明,因此它们被称为暴露。不过,我不确定您所说的不安全。【参考方案7】:@David Titarenco nailed it here,但让我至少清理一下让它看起来不错,并同时显示min()
和 max()
,以便从这里复制和粘贴更容易。 :)
2020 年 4 月 25 日更新:我还添加了第 3 节来展示如何使用 C++ 模板完成此操作,作为对同时学习 C 和 C++ 或从一种过渡到另一种的人进行有价值的比较。我已尽我最大的努力做到彻底、真实和正确,以使这个答案成为我可以一次又一次地回顾的规范参考,我希望你发现它和我一样有用。
1。旧的 C 宏方式:
这种技术很常用,受到那些知道如何正确使用它的人的推崇,这是“事实上的”做事方式,如果使用得当,也可以很好地使用,但是有缺陷(想一想:double-evaluation side effect) 如果你曾传递 包括变量赋值的表达式 进行比较:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
2。新改进的 gcc "statement expression" 方式:
这种技术避免了上述“双重评估”的副作用和错误,因此被认为是更好、更安全和“更现代”的 GCC C 方法。期望它可以与 gcc 和 clang 编译器一起使用,因为 clang 在设计上是与 gcc 兼容的(请参阅此答案底部的 clang 注释)。
但是:请注意“variable shadowing”效果,因为语句表达式显然是内联的,因此没有自己的局部变量范围!
#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's documentation 是这样说的:
复合语句中的最后一件事应该是一个表达式,后跟一个分号;此子表达式的值用作整个构造的值。 (如果您在大括号中最后使用某种其他类型的语句,则该构造的类型为 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++ 标准库中,它们的定义与我在上面的定义略有不同。 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
是class
的别名(因此无论您说<typename T>
还是<class T>
,它们的用法都是相同的),因为它后来在C++ 模板发明后得到承认,模板类型可能是常规类型(int
、float
等)而不仅仅是类类型。
在这里你可以看到输入类型和返回类型都是const T&
,意思是“对T
类型的持续引用”。这意味着输入参数和返回值是按引用传递,而不是按值传递。这就像通过指针传递,并且对于大型类型(例如类对象)更有效。函数 modifies the function itself 的 constexpr
部分表示函数必须能够在编译时进行评估(至少如果提供了constexpr
输入参数),但如果它不能在编译时评估,然后它默认返回到运行时评估,就像任何其他普通函数一样。
constexpr
C++ 函数的编译时方面使它有点像 C 宏,如果 constexpr
函数可以进行编译时评估,它将在编译时完成,与 MIN()
或 MAX()
宏替换相同,也可能在 C 或 C++ 的编译时完全评估。有关此 C++ 模板信息的其他参考,请参见下文。
参考资料:
-
https://gcc.gnu.org/onlinedocs/gcc/Typeof.html#Typeof
https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs
MIN and MAX in C
2020 年 4 月添加了其他 C++ 模板参考:
*****Wikipedia: Template (C++)
(我自己的问答):Why is `constexpr` part of the C++14 template prototype for `std::max()`?
Difference between `constexpr` and `const`
Clang 注释from Wikipedia:
[Clang] 旨在充当 GNU 编译器集合 (GCC) 的直接替代品,支持其大部分编译标志和非官方语言扩展。
相关:
-
[我的回答]Rounding integer division (instead of truncating) - 我在这里也使用宏、gcc/clang 语句表达式和 C++ 模板。
【讨论】:
当这个问题询问 c 时,关于 c++ 的巨大部分的意义何在?它所做的只是复制std::max()
和 std::min()
已经在做的事情。
@qwr:重点:1) 学习,2) 复制std::max()
和std::min()
已经在做的事情,这样你就可以了解它们是如何工作的(学习),3) 学习从 C 到 C++,因为许多人从 C 开始,然后也需要学习 C++,反之亦然,因此将 C 和 C++ 的答案放在一起对使用这两种语言编写的任何人都有帮助。例如我自己:我是一名嵌入式软件工程师。有时我在 C 代码库中工作,我来这里逐字复制和粘贴我的宏或 gcc 语句表达式答案,有时我在 C++ 代码库中工作并在这里阅读我的笔记以记住模板。
这完全不适合这个问题
我完全不同意:任何回答者都不应因为付出额外的努力并给出比所要求的更彻底的答案而受到惩罚。许多人登陆这个页面,他们从额外的信息中受益。但是,如果您不喜欢它,请在到达那部分后闭上眼睛。当您充分向下滚动页面时重新打开它们。我做了粗体标题以明确 C++ 部分何时开始,因此如果它不适用于他们的情况,可以很容易地忽略它。
您应该将模板版本写成min(T &&a, T &&b)
,这样如果提供右值引用或lvaues,它可以更快地工作。【参考方案8】:
我写了这个适用于 MSVC、GCC、C 和 C++ 的 version。
#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
【讨论】:
我投了赞成票,但以下划线开头后跟大写字母的标识符被保留。【参考方案9】:如果您需要 min/max 以避免昂贵的分支,则不应使用三元运算符,因为它会编译为跳转。下面的链接描述了一种在没有分支的情况下实现最小/最大函数的有用方法。
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
【讨论】:
如果编译器足够聪明,它可以避免分支 如果开启优化,在大多数情况下,所有现代编译器都会发出条件移动而不是分支,因此使用这样的 hack 没有什么意义。 绝对正确,我不知道我当时在看什么,已经有一段时间了。 gcc 和 clang 在 x86 和 armv7a 上都避免使用 -O 的分支。【参考方案10】:值得指出的是,我认为如果你用三元运算来定义min
和max
,比如
#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,我在这里给出了更好的答案***.com/a/30915238/2542702【参考方案11】:
看起来 Windef.h
(a la #include <windows.h>
) 有 max
和 min
(小写) 宏,它们也存在“双重评估”困难,但它们适用于那些没有不想自己重新推出:)
【讨论】:
【参考方案12】:我知道那个人说“C”... 但如果有机会,请使用 C++ 模板:
template<class T> T min(T a, T b) return a < b ? a : b;
类型安全,其他cmets中提到的++没有问题。
【讨论】:
参数应该是常量引用,你永远不知道用户会通过什么。 这样的功能已经标准化了(std::min)。 C++ 有很多标准函数用于大多数正常用途,不要重新发明***。但是MS also defines their own min/max 有时会导致问题【参考方案13】:旧 GCC 扩展:运算符 <?, >?, <?=, >?=
在一个非常旧的 GCC 版本中,有运算符 <?, >?
(参见 here,这里是 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,因为我认为这些运营商是天才。
【讨论】:
【参考方案14】:a
和b
两个整数的最大值为(int)(0.5((a+b)+abs(a-b)))
。这也可以与 (double)
和 fabs(a-b)
一起用于双打(类似于浮点数)
【讨论】:
我不确定它是否适用于非整数。浮点数学具有非线性精度。 扩展@Treesrule14 的评论:这不起作用,因为计算机不像数学家那样对待数字。浮点数存在舍入问题,因此您不太可能得到正确答案。即使您使用整数数学,MAX_INT+MAX_INT 也会给出 -2,因此使用您的公式的 max(MAX_INT, MAX_INT) 会得出 -1。【参考方案15】:最简单的方法是将其定义为.h
文件中的全局函数,并在您需要时调用它,如果您的程序是具有大量文件的模块化程序。如果没有,double MIN(a,b)return (a<b?a:b)
是最简单的方法。
【讨论】:
@technosaurus,你的回复真的没有帮助。 Tur1ing,似乎函数定义完全错误(输入参数上缺少类型,return 语句后缺少分号),并且将 int 输入转换为 double 是一种糟糕的处理方式,因此类型不应该是 double。在这里定义或语句表达式会更好(例如:see here),但如果是函数,请考虑为 int32_t 类型创建一个函数,为 uint32_t 类型创建一个函数,为 float 或 double 类型创建一个函数,总共3 种不同的功能。 @GabrielStaples 这个答案应该被标记为不是答案——没有任何帮助。虽然它可以作为一个例子,说明如何在最小的空间中犯最大的错误。在头文件中推荐全局函数(甚至不是静态内联?)将破坏具有 2 个以上编译单元的代码,甚至无法编译,将函数命名为宏,隐含的整数(如 1989 年),无缘无故返回双精度数,隐含最多会导致警告的强制转换......最重要的是它没有回答问题 - 不是通用的,不是类型安全的,而且绝对不是最好的方法 这些问题中的每一个都值得进一步批评,但无法详细说明。以上是关于C中的最小值和最大值的主要内容,如果未能解决你的问题,请参考以下文章