如果有的话,在 C中定义MIN
和定义在哪里?MAX
实现这些的最佳方法是什么,尽可能通用并尽可能安全地输入?(首选主流编译器的编译器扩展/内置。)
如果有的话,在 C中定义MIN
和定义在哪里?MAX
实现这些的最佳方法是什么,尽可能通用并尽可能安全地输入?(首选主流编译器的编译器扩展/内置。)
如果有的话,在 C中定义
MIN
和定义在哪里?MAX
他们不是。
实现这些的最佳方法是什么,尽可能通用且类型安全(首选主流编译器的编译器扩展/内置)。
作为函数。我不会使用宏#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
,尤其是如果您打算部署代码。要么自己编写,要么使用标准fmax
或之类的东西,或者在GCC 语句表达式中使用GCC 的 typeof(你也获得类型安全奖励)fmin
来修复宏:
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
每个人都说“哦,我知道双重评估,这没问题”,几个月后,您将连续数小时调试最愚蠢的问题。
注意使用__typeof__
代替typeof
:
如果您正在编写包含在 ISO C 程序中时必须工作的头文件,请编写
__typeof__
而不是typeof
.
它也在 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))
源存储库在这里:
在 C++ 中有一个std::min
and std::max
,但是 AFAIK,在 C 标准库中没有等价物。您可以使用宏自己定义它们,例如
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
但是,如果您编写类似MAX(++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
. 为了防止宏进行这种具有潜在危险的类型提升,使用了最终类型转换为预期类型。
基本原理
我们希望宏的两个参数具有相同的类型。如果其中一个属于不同类型,则宏不再是类型安全的,因为 like 运算符?:
将产生隐式类型提升。因为它确实如此,所以我们也总是需要将最终结果转换回预期的类型,如上所述。
只有一个参数的宏可以用更简单的方式编写。但是如果有 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;
}
由于最近的发展,这是一个较晚的答案。由于 OP 接受了依赖于非便携式 GCC(和 clang)扩展的答案typeof
- 或“干净的” ISO C - 从gcc-4.9__typeof__
开始有更好的解决方案。
#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })
此扩展的明显好处是每个宏参数仅扩展一次,与__typeof__
解决方案不同。
__auto_type
是 C++11 的有限形式auto
。它不能(或不应该?)在 C++ 代码中使用,尽管没有充分的理由不使用auto
使用 C++11 时的高级类型推断功能。
也就是说,我假设当宏包含在范围内时使用此语法没有问题extern "C" { ... }
;例如,来自 C 标头。AFAIK,这个扩展还没有找到它的方式信息铿锵
我不认为它们是标准化的宏。已经有用于浮点的标准化函数,fmax
并且fmin
(fmaxf
对于浮点数和fmaxl
长双精度数)。
只要您知道副作用/双重评估的问题,您就可以将它们实现为宏。
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
在大多数情况下,您可以将其留给编译器来确定您要执行的操作并尽可能优化它。虽然这在使用 like 时会引起问题MAX(i++, j++)
,但我怀疑是否有必要一次性检查增量值的最大值。先增加,再检查。
@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++ 模板版本以保持完整性,并使其成为更规范和彻底的答案。
以下是 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++ 模板的更多信息: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
”。这意味着输入参数和返回值是通过引用传递而不是通过值传递。这就像通过指针传递,并且对于大型类型(例如类对象)更有效。函数的constexpr
一部分修改了函数本身并表明该函数必须能够在编译时被评估(至少如果提供了constexpr
输入参数),但如果它不能在编译时被评估,那么它默认回到一个运行时评估,就像任何其他正常功能一样。
C++ 函数的编译时方面constexpr
使它有点像 C 宏,如果一个constexpr
函数可以在编译时评估,它将在编译时完成,就像一个MIN()
或MAX()
宏替换可能一样也可以在 C 或 C++ 的编译时进行全面评估。有关此 C++ 模板信息的其他参考资料,请参见下文。
来自维基百科的Clang 注释:
[Clang] 旨在充当 GNU 编译器集合 (GCC) 的直接替代品,支持其大部分编译标志和非官方语言扩展。
我编写的这个版本适用于 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
如果您需要 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)
看起来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; }
键入安全,其他评论中提到的 ++ 没有问题。
<?, >?, <?=, >?=
在一个非常旧的 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,因为我认为这些运营商是天才。
两个整数的最大值a
和b
是(int)(0.5((a+b)+abs(a-b)))
。这也可能适用于(double)
和fabs(a-b)
双打(类似于浮动)
最简单的方法是将其定义为.h
文件中的全局函数,并在需要时调用它,如果您的程序是具有大量文件的模块化程序。如果没有,double MIN(a,b){return (a<b?a:b)}
是最简单的方法。