C中的MIN和MAX

Mat*_*ner 265 c max min c-preprocessor

在C中的位置MINMAX定义,如果有的话?

实现这些的最佳方式是什么,尽可能通用和安全?(首选编译器扩展/内置主流编译器.)

Dav*_*nco 352

在C中的位置MINMAX定义,如果有的话?

他们不是.

实现这些的最佳方法是什么,尽可能通用和类型安全(首选编译器扩展/内置主流编译器).

作为功​​能.我不会使用宏#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)),特别是如果您计划部署代码.无论是写自己的,使用类似标准fmaxfmin,或使用固定宏海湾合作委员会的typeof(你的类型安全奖励太):

 #define max(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })
Run Code Online (Sandbox Code Playgroud)

每个人都说"哦,我知道双重评估,这没有问题",几个月后,你将在几个小时内调试最愚蠢的问题.

注意使用__typeof__而不是typeof:

如果您正在编写包含在ISO C程序中必须工作的头文件,请编写__typeof__而不是 typeof.

  • 你知道,如果gcc有一个警告:'警告:带有副作用的表达式在使用点被多重评估',它会非常方便... (65认同)
  • @caf:这不需要预处理器有更复杂的C语法知识吗? (20认同)
  • @Soumen例如,如果你调用`MIN(x ++,y ++)`,预处理器将生成以下代码`(((x ++)<(y ++))?(x ++):( y ++))`.所以,`x`和`y`将增加两次. (7认同)
  • @dreamlax我曾经看过一个人用`MAX(someUpperBound,someRandomFunction())`将一个随机值限制到某个上限的情况.这是一个糟糕的想法,但它甚至没有工作,因为他使用的'MAX`有双重评估问题,所以他得到的结果与最初评估的随机数不同. (4认同)
  • 经过多次尝试弄清楚,我认为无论如何都不会在VC++中做到这一点,但你最好的办法是尝试搞乱MSVC++ 2010新的`decltype`关键字 - 但即便如此,Visual Studio也无法复合宏中的语句(和`decltype`无论如何都是C++),即GCC的`({...})`语法,所以我很确定它是不可能的.关于这个问题,我没有看过任何其他编译器,抱歉路德:S (3认同)
  • @dreamlax:我认为当多次使用参数时,预处理器可以在每个宏扩展时留下注释,然后编译器可以使用注释。 (2认同)
  • 另请参见http://stackoverflow.com/questions/5595593/min-macro-in-kernel-h - 与此类似,但对类型安全性进行了额外检查. (2认同)
  • `警告:ISO C禁止在表达式[-Wpedantic]中使用支撑组 (2认同)
  • 你本来可以停在“作为功能”,那一刻你赢得了我的支持:-) (2认同)
  • @Antonio in MIN(x++,y++) 说 x &lt; y,那么 x 将增加两次,y 将仅增加一次。这 '?' 是一个序列点,因此所有副作用都得到解决,然后仅执行由 ':' 分隔的两个语句之一。 (2认同)

Mik*_*kel 85

它也在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.
Run Code Online (Sandbox Code Playgroud)

在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))
Run Code Online (Sandbox Code Playgroud)

源存储库在这里:

  • 稍等片刻。它不会阻止双重评估,是吗?:3 (4认同)

dan*_*n04 70

有一个std::minstd::maxC++,但AFAIK,在C标准库中没有等价物.您可以使用宏来自己定义它们

#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Run Code Online (Sandbox Code Playgroud)

但如果你写一些像这样的东西会导致问题MAX(++a, ++b).

  • @Makouda:宏中的额外括号有助于避免运算符优先级问题.例如,考虑`#define MULT(x,y)x*y`.然后`MULT(a + b,a + b)`扩展为'a + b*a + b`,由于优先级,它解析为'a +(b*a)+ b`.这不是程序员可能想要的. (72认同)
  • 为什么要放太多括号??? 我发现了一个测验,他们说`#define MIN(A,B)((A <B)?答:B)`不是灵活的方式,为什么??? (9认同)
  • @WingerSendon:没有;逗号运算符可以。 (3认同)
  • 但是你不能将带有逗号运算符的表达式作为参数传递给宏,除非你无论如何都用括号括起来 (2认同)

Lun*_*din 21

避免使用非标准编译器扩展,并将其作为纯标准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))
Run Code Online (Sandbox Code Playgroud)

用法

MAX(int, 2, 3)
Run Code Online (Sandbox Code Playgroud)

说明

宏MAX根据type参数创建另一个宏.如果针对给定类型实现此控制宏,则用于检查两个参数的类型是否正确.如果type不支持,则会出现编译器错误.

如果x或y的类型不正确,则ENSURE_宏中将出现编译器错误.如果支持更多类型,可以添加更多此类宏.我假设只使用算术类型(整数,浮点数,指针等),而不是结构或数组等.

如果所有类型都正确,则将调用GENERIC_MAX宏.每个宏参数都需要额外的括号,作为编写C宏时的常用标准预防措施.

然后是C中隐式类型促销的常见问题.?:运算符将第2和第3个操作数相互平衡.例如,结果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))  \
          )
Run Code Online (Sandbox Code Playgroud)

问题是如果上面的宏被调用为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;
}
Run Code Online (Sandbox Code Playgroud)

  • 顺便说一句,“GENERIC_MAX”宏是一个坏主意,你只需尝试“GENERIC_MAX(var++, 7)”即可找出原因:-)如今(尤其是在高度优化/内联编译器的情况下),宏几乎应该被降级仅限于简单的形式。类函数的函数作为函数更好,而值组的函数作为枚举更好。 (2认同)

Bre*_*ale 21

由于最近的发展,这是一个迟到的答案.由于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; })
Run Code Online (Sandbox Code Playgroud)

这个扩展的明显好处是每个宏参数只扩展一次,与__typeof__解决方案不同.

__auto_type是C++ 11的有限形式auto.它不能(或不应该?)在C++代码中使用,尽管auto在使用C++ 11时没有充分的理由不使用高级类型的推理功能.

也就是说,我认为当宏包含在extern "C" { ... }作用域中时,使用此语法没有问题; 例如,来自C头.AFAIK,这个扩展没有找到它的方式信息clang


Gab*_*les 20

@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))
Run Code Online (Sandbox Code Playgroud)

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;       \
})
Run Code Online (Sandbox Code Playgroud)

请注意,在 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;
}
Run Code Online (Sandbox Code Playgroud)

在此处阅读有关 C++ 模板的更多信息:维基百科:模板 (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);
Run Code Online (Sandbox Code Playgroud)

请注意,关键字typename是一个别名class(所以它们的用法是相同的,你是否说<typename T><class T>),因为它的C ++模板的发明后来经过确认,该模板类型可能是一个普通类型(intfloat,等),而不是只类类型。

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

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

来自维基百科的Clang 注释:

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

有关的:

  1. [我的回答]舍入整数除法(而不是截断) - 我也在这里使用宏、gcc/clang 语句表达式和 C++ 模板。

  • 当这个问题问的是 c 的时候,关于 c++ 的大篇幅有什么意义呢?它所做的只是复制 `std::max()` 和 `std::min()` 已经做的事情。 (5认同)
  • 我完全不同意:任何回答者都不应该因为加倍努力并给出比所要求的更彻底的答案而受到惩罚。许多登陆此页面的人都受益于额外的信息。但如果你不喜欢它,那么一旦你读到那部分就闭上眼睛。当您充分向下滚动页面后,重新打开它们。我用粗体标题来明确 C++ 部分何时开始,因此如果它不适合他们的情况,人们可以很容易地忽略它。 (2认同)

dre*_*lax 19

我不认为它们是标准化的宏.有浮点已经标准化的功能,fmax并且fmin(和fmaxf对花车,和fmaxl长期双打).

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

#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
Run Code Online (Sandbox Code Playgroud)

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


Mat*_*ner 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
Run Code Online (Sandbox Code Playgroud)

  • 我投了赞成票,但以下划线开头、后跟大写字母的标识符被保留。 (3认同)

cib*_*cib 8

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

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

  • 绝对正确,我不知道我当时在看什么,已经有一段时间了。gcc 和 clang 都避免在 x86 和 armv7a 上使用 -O 进行分支。 (3认同)
  • 如果编译器足够聪明,它可以避免分支 (2认同)
  • 如果启用了优化,在大多数情况下,所有现代编译器都会发出条件移动而不是分支,因此使用这样的黑客几乎没有意义. (2认同)

rog*_*ack 5

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


Z b*_*son 5

值得指出的是,我认为如果您定义minmax使用三元运算,例如

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
Run Code Online (Sandbox Code Playgroud)

然后为特殊情况获得相同的结果fmin(-0.0,0.0)fmax(-0.0,0.0)您需要交换参数

fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

670996 次

最近记录:

6 年,1 月 前