370

如果有的话,在 C中定义MIN和定义在哪里?MAX

实现这些的最佳方法是什么,尽可能通用并尽可能安全地输入?(首选主流编译器的编译器扩展/内置。)

4

15 回答 15

465

如果有的话,在 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.

于 2010-08-09T05:13:07.240 回答
109

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

源存储库在这里:

于 2010-08-09T05:57:53.390 回答
95

在 C++ 中有一个std::minand std::max,但是 AFAIK,在 C 标准库中没有等价物。您可以使用宏自己定义它们,例如

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

但是,如果您编写类似MAX(++a, ++b).

于 2010-08-09T04:56:57.030 回答
26

避免非标准编译器扩展,并将其实现为纯标准 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;
}
于 2015-06-18T14:29:59.367 回答
24

由于最近的发展,这是一个较晚的答案。由于 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,这个扩展还没有找到它的方式信息铿锵

于 2015-08-20T00:18:05.023 回答
23

我不认为它们是标准化的宏。已经有用于浮点的标准化函数,fmax并且fminfmaxf对于浮点数和fmaxl长双精度数)。

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

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

在大多数情况下,您可以将其留给编译器来确定您要执行的操作并尽可能优化它。虽然这在使用 like 时会引起问题MAX(i++, j++),但我怀疑是否有必要一次性检查增量值的最大值。先增加,再检查。

于 2010-08-09T04:58:50.800 回答
23

@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++ 模板版本以保持完整性,并使其成为更规范和彻底的答案。

以下是 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++ 模板发明后承认,模板类型可能是常规类型(intfloat等),而不仅仅是一类类型。

在这里您可以看到输入类型以及返回类型都是const T&,这意味着“对类型的常量引用T”。这意味着输入参数和返回值是通过引用传递而不是通过值传递。这就像通过指针传递,并且对于大型类型(例如类对象)更有效。函数的constexpr一部分修改了函数本身并表明该函数必须能够在编译时被评估(至少如果提供了constexpr输入参数),但如果它不能在编译时被评估,那么它默认回到一个运行时评估,就像任何其他正常功能一样。

C++ 函数的编译时方面constexpr使它有点像 C 宏,如果一个constexpr函数可以在编译时评估,它将在编译时完成,就像一个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中的最小值和最大值
  4. 2020 年 4 月添加的其他 C++ 模板参考:
  5. *****维基百科:模板 (C++) <-- 关于 C++ 模板的重要附加信息!
  6. (我自己的问题和答案):为什么 `constexpr` 是`std::max()` 的 C++14 模板原型的一部分?
  7. `constexpr` 和 `const` 之间的区别

来自维基百科的Clang 注释:

[Clang] 旨在充当 GNU 编译器集合 (GCC) 的直接替代品,支持其大部分编译标志和非官方语言扩展。

有关的:

  1. [我的回答]舍入整数除法(而不是截断) ——我在这里也使用宏、gcc/clang 语句表达式和 C++ 模板。
于 2019-10-24T01:06:41.257 回答
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
于 2012-01-03T12:09:53.773 回答
8

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

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

于 2011-09-19T18:13:05.887 回答
5

值得指出的是,我认为如果您定义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)
于 2015-06-18T12:42:57.250 回答
4

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

于 2012-04-24T18:54:21.327 回答
1

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

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

键入安全,其他评论中提到的 ++ 没有问题。

于 2013-03-14T11:23:36.597 回答
1

旧 GCC 扩展:运算符<?, >?, <?=, >?=

在一个非常旧的 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,因为我认为这些运营商是天才。

于 2021-01-05T06:43:51.980 回答
-1

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

于 2013-07-03T13:34:38.890 回答
-4

最简单的方法是将其定义为.h文件中的全局函数,并在需要时调用它,如果您的程序是具有大量文件的模块化程序。如果没有,double MIN(a,b){return (a<b?a:b)}是最简单的方法。

于 2018-04-12T20:36:13.663 回答