【发布时间】:2011-03-27 03:06:08
【问题描述】:
如果有的话,MIN 和 MAX 在 C 中定义在哪里?
实现这些的最佳方法是什么,尽可能通用并尽可能安全地输入? (首选主流编译器的编译器扩展/内置。)
【问题讨论】:
-
有人可以检查this 并判断它是宏还是函数?我的意思是,在
min(x++, y++)行中,如果我使用这个min,x 和y 会增加一到两次。
标签: c max min c-preprocessor
如果有的话,MIN 和 MAX 在 C 中定义在哪里?
实现这些的最佳方法是什么,尽可能通用并尽可能安全地输入? (首选主流编译器的编译器扩展/内置。)
【问题讨论】:
min(x++, y++) 行中,如果我使用这个min,x 和y 会增加一到两次。
标签: c max min c-preprocessor
如果有的话,
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.
【讨论】:
warning: expression with side-effects multiply evaluated by macro...
decltype 关键字——但即便如此,Visual Studio 不能在宏中执行复合语句(而且decltype 无论如何都是C++),即GCC 的({ ... }) 语法,所以我很确定这是不可能的。我没有看过任何其他关于这个问题的编译器,对不起 Luther :S
MAX(someUpperBound, someRandomFunction()) 将随机值限制在某个上限。这是一个糟糕的想法,但它甚至没有用,因为他使用的 MAX 存在双重评估问题,所以他最终得到的随机数与最初评估的随机数不同。
MIN(x++, y++),预处理器将生成以下代码(((x++) < (y++)) ? (x++) : (y++))。因此,x 和 y 将增加两次。
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))
源代码库在这里:
【讨论】:
openSUSE/Linux 3.1.0-1.2-desktop/gcc version 4.6.2 (SUSE Linux)。 :) 不好,它不便携。
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) 不是一种灵活的方式,为什么???
#define MULT(x, y) x * y。然后MULT(a + b, a + b) 扩展为a + b * a + b,由于优先级,它解析为a + (b * a) + b。这可能不是程序员的本意。
避免使用非标准编译器扩展并将其实现为纯标准 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) 即可找出原因 :-) 如今(尤其是在高度优化/内联编译器的情况下),宏应该很漂亮很多都只能归结为简单的形式。类函数更适合作为函数,值组更适合作为枚举。
由于最近的发展,这是一个较晚的答案。由于 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,这个扩展程序没有找到它的方式信息铿锵声
【讨论】:
clang在2016年左右开始支持__auto_type(见patch)。
c-preprocessor 标签。即使使用上述关键字,也不能保证内联函数,除非使用 gcc 的 __always_inline__ 属性。
({ ... }) 扩展。我不认为它比带有typeof(带或不带下划线)的版本更便携。
我不认为它们是标准化的宏。已经有用于浮点的标准化函数,fmax 和 fmin(fmaxf 用于浮点,fmaxl 用于长双精度)。
只要您意识到副作用/双重评估的问题,您就可以将它们实现为宏。
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
在大多数情况下,您可以将其留给编译器来确定您要执行的操作并尽可能优化它。虽然这在使用 MAX(i++, j++) 时会引起问题,但我怀疑是否有必要一次性检查增量值的最大值。先增加,再检查。
【讨论】:
@David Titarenco nailed it here,但让我至少清理一下让它看起来不错,并同时显示min() 和 max(),以便从这里复制和粘贴更容易。 :)
2020 年 4 月 25 日更新:我还添加了第 3 节来展示如何使用 C++ 模板完成此操作,作为对同时学习 C 和 C++ 或从一种过渡到另一种的人进行有价值的比较。我已尽我最大的努力做到彻底、真实和正确,以使这个答案成为我可以一次又一次地回顾的规范参考,我希望你发现它和我一样有用。
这种技术很常用,受到那些知道如何正确使用它的人的推崇,这是“事实上的”做事方式,如果使用得当,也可以很好地使用,但是有缺陷(想一想:double-evaluation side effect) 如果你曾传递 包括变量赋值的表达式 进行比较:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
这种技术避免了上述“双重评估”的副作用和错误,因此被认为是更好、更安全和“更现代”的 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,因此实际上没有值。)
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++ 模板信息的其他参考,请参见下文。
Clang 注释from Wikipedia:
[Clang] 旨在充当 GNU 编译器集合 (GCC) 的直接替代品,支持其大部分编译标志和非官方语言扩展。
【讨论】:
std::max() 和 std::min() 已经在做的事情。
std::max() 和std::min() 已经在做的事情,这样你就可以了解它们是如何工作的(学习),3) 学习从 C 到 C++,因为许多人从 C 开始,然后也需要学习 C++,反之亦然,因此将 C 和 C++ 的答案放在一起对使用这两种语言编写的任何人都有帮助。例如我自己:我是一名嵌入式软件工程师。有时我在 C 代码库中工作,我来这里逐字复制和粘贴我的宏或 gcc 语句表达式答案,有时我在 C++ 代码库中工作并在这里阅读我的笔记以记住模板。
min(T &&a, T &&b),这样如果提供右值引用或lvaues,它可以更快地工作。
我写了这个适用于 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
【讨论】:
如果您需要 min/max 以避免昂贵的分支,则不应使用三元运算符,因为它会编译为跳转。下面的链接描述了一种在没有分支的情况下实现最小/最大函数的有用方法。
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
【讨论】:
值得指出的是,我认为如果你用三元运算来定义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)
【讨论】:
fmin(3.0,NaN)==fmin(NaN,3.0)==fmax(3.0,NaN)==fmax(NaN,3.0)==3.0
看起来 Windef.h (a la #include <windows.h>) 有 max 和 min (小写) 宏,它们也存在“双重评估”困难,但它们适用于那些没有不想自己重新推出:)
【讨论】:
我知道那个人说“C”... 但如果有机会,请使用 C++ 模板:
template<class T> T min(T a, T b) { return a < b ? a : b; }
类型安全,其他cmets中提到的++没有问题。
【讨论】:
<?, >?, <?=, >?=
在一个非常旧的 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,因为我认为这些运营商是天才。
【讨论】:
a 和b 两个整数的最大值为(int)(0.5((a+b)+abs(a-b)))。这也可以与 (double) 和 fabs(a-b) 一起用于双打(类似于浮点数)
【讨论】:
最简单的方法是将其定义为.h 文件中的全局函数,并在您需要时调用它,如果您的程序是具有大量文件的模块化程序。如果没有,double MIN(a,b){return (a<b?a:b)} 是最简单的方法。
【讨论】: