Answers:
在哪里
MIN
和MAX
在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
。
decltype
关键字-但是即使如此,Visual Studio也无法在宏中执行复合语句(decltype
无论如何都是C ++),即GCC的({ ... })
语法,所以我很确定这是不可能的。对于这个问题,我还没有看过其他编译器,对不起路德:S
MAX(someUpperBound, someRandomFunction())
将随机值限制为某个上限的情况。这是一个糟糕的主意,但它甚至也没有用,因为MAX
他使用的是双重评估问题,因此他得到的随机数与最初评估的随机数不同。
MIN(x++, y++)
预处理器,则会生成以下代码(((x++) < (y++)) ? (x++) : (y++))
。所以,x
和y
将两次递增。
它还在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))
源代码库在这里:
openSUSE/Linux 3.1.0-1.2-desktop
/ gcc version 4.6.2 (SUSE Linux)
。:)不好,它不能移植。
在C ++中有一个std::min
和std::max
,但是AFAIK在C标准库中没有等效项。您可以自己定义宏,例如
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
但这会导致问题,如果您编写类似的内容MAX(++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中存在隐式类型提升的常见问题。?:
运算符使第二和第三操作数彼此平衡。例如,的结果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)
找出原因:-)如今(尤其是在大量优化/内联的编译器中),宏应该只被简化为简单形式。类似函数的函数最好作为函数,而值组的函数最好作为枚举。
我认为它们不是标准化的宏。已经有针对浮点fmax
和fmin
(以及fmaxf
针对浮点和fmaxl
长双精度)的标准化函数。
只要您知道副作用/双重评估的问题,就可以将它们实现为宏。
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
在大多数情况下,您可以将其留给编译器来确定要执行的操作,并尽可能地对其进行优化。尽管这在使用时会引起问题MAX(i++, j++)
,但我怀疑是否一过便需要检查增量值的最大值。首先增加,然后检查。
由于最近的发展,这是一个较晚的答案。由于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,此扩展程序未找到信息提示声
c-preprocessor
标签。除非使用gcc的__always_inline__
属性,否则即使使用所述关键字也不能保证内联函数。
我写了这个版本,对于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
如果您需要最小/最大以避免一个昂贵的分支,则不应该使用三元运算符,因为它会编译为跳转。下面的链接描述了一种无需分支即可实现最小/最大函数的有用方法。
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@David Titarenco在这里钉了钉子,但让我至少至少要对其进行清理以使其看起来更漂亮,并同时展示它们min()
, max()
以使从此处复制和粘贴更加容易。:)
2020年4月25日更新:我还添加了第3节,以说明如何使用C ++模板完成此操作,作为对同时学习C和C ++或从一种过渡到另一种的人的宝贵比较。我已尽力做到彻底,真实和正确,以使此答案成为一个规范的参考,我可以一再再来,我希望您发现它和我一样有用。
这项技术是常用的,受到那些知道如何正确使用它的人的推崇,是“事实上”的做事方式,如果使用得当,可以很好地使用,但是如果您遇到问题,可以考虑使用越野车(请考虑:双重评估的副作用)曾经通过传递包含变量赋值的表达式来进行比较:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
此技术避免了上述“双重评估”的副作用和错误,因此被认为是实现此目的的优越,安全和“更现代”的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,因此实际上没有任何值。)
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 ++模板的发明后来经过确认,该模板类型可能是一个普通类型(int
,float
,等),而不是只类类型。
在这里,您可以看到两种输入类型以及返回类型均为const T&
,这意味着“对类型的恒定引用T
”。这意味着输入参数和返回值是通过引用传递的,而不是通过value传递的。这就像通过指针传递一样,并且对于大型类型(例如类对象)更有效。constexpr
函数的一部分修改了函数本身,并指示该函数必须能够在编译时进行评估(至少在提供constexpr
输入参数的情况下),但是如果无法在编译时进行评估,则默认返回到像任何其他正常功能一样,运行时评估。
constexpr
C ++函数的编译时特性使其类似于C宏,因为如果可以对constexpr
函数进行编译时求值,则将在编译时完成,就像可能进行a MIN()
或MAX()
宏替换一样也可以在编译时使用C或C ++进行全面评估。有关此C ++模板信息的其他参考,请参见下文。
维基百科的 lang语:
[Clang]旨在替代GNU编译器集合(GCC),支持其大多数编译标志和非官方语言扩展。
值得指出的是,我认为如果您定义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
我知道那个人说“ C” ...但是如果有机会,请使用C ++模板:
template<class T> T min(T a, T b) { return a < b ? a : b; }
键入safe,其他注释中提到的++都没有问题。
两个整数的最大a
和b
为(int)(0.5((a+b)+abs(a-b)))
。这可能也适用于双打(double)
和fabs(a-b)
双打(对于浮点数类似)
最简单的方法是将其定义为.h
文件中的全局函数,并在程序包含大量文件的情况下随时调用它。如果不是,那double MIN(a,b){return (a<b?a:b)}
是最简单的方法。
warning: expression with side-effects multiply evaluated by macro
在使用时...