9
\$\开始组\$

实现这一点的最简单方法是为每种类型使用单独的函数,并使用后缀来区分它们,如下所示:

最大i();//整数最大值(_ui);//无符号整型//对于其他类型,如此类推。

但这太麻烦了,记不住了。人们可以选择类似函数的宏:

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

但这些方法的缺点是参数会被评估两次。这可能会在以下通话中造成一些问题:

最大值(rand(),*p++)最小值(--a,b++)

但在宏之前有一条注释:

/*XXX:不要向这些宏传递带有副作用的参数*/

将负担从实现者身上转移到开发人员身上(假设他们是不同的人)。

但这些宏指令仍然不受欢迎。避免双重评估部分的一种方法是使用C23汽车和GNU C的复合表达式语句(由Clang和GCC支持,我不知道其他人):

#如果定义(__GNUC__)||定义(__clang__)#定义MAX(a,b)({\自动x=(a)\自动y=(b)\x>y?x:y;})#定义MIN(a,b)({\自动x=(a)\自动y=(b)\x<y?x:y;})#结尾

这些人只评估他们的论点一次。但复合表达式语句不是ISO C的一部分。

在这种情况下,我们只剩下C11_通用:

#定义MAX(a,b)_通用(a)+(b)\带符号字符:max_sc\无符号字符:max_uc\字符:max_c\short int:max_si\无符号短int:max_usi\整数:max_i\无符号整数:max_ui\long int:max_li\无符号长整型:max_uli\long-long-int:max_lli\无符号long long int:max_lli\浮点:max_f\双倍:max_d\长双精度:max_ld)(a,b)#定义最小值(a,b)_通用(a)+(b)\带符号字符:min_sc\无符号字符:min_uc\字符:min_c\短整数:min_si\无符号短整型:min_usi\整数:min_i\无符号int:min_ui\long int:min_li\无符号长整型:min_uli\long long int:min_lli\无符号long long int:min_ulli\浮点:min_f\双精度:min_d\长双精度:min_ld)(a,b)/*或者只在此处提供声明并具有相应的源文件所有的定义*/#定义gen_max(类型,后缀)\[[gnu::always_inline,gnu:∶const]]静态内联max_##后缀(类型a,类型b)\{返回a>b?a:b;}#定义gen_min(类型,后缀)\[[gnu::always_inline,gnu::const]]静态内联min_##后缀(类型a,类型b)\{返回a>b?a:b;}gen_max(带符号字符,sc)gen_max(无符号字符,uc)gen_max(字符,c)gen_max(短int,si)gen_max(无符号短整型,usi)发电机最大值(int,i)gen_max(无符号int,ui)gen_max(长整型,li)gen_max(无符号长整型,uli)gen_max(长整型int,lli)gen_max(无符号长整型int,ulli)发电机最大值(浮点,f)发电机最大值(双,d)gen_max(长双精度,ld)gen_min(带符号字符,sc)gen_min(无符号字符,uc)gen_min(字符,c)gen_min(短int,si)gen_min(无符号短int,usi)发电机最小值(int,i)gen_min(无符号int,ui)gen_min(长整型,li)gen_min(无符号长整型,uli)gen_min(long-long int,lli)gen_min(无符号长整型int,ulli)gen_min(浮点,f)gen_min(双,d)gen_min(长双精度,ld)

这既安全(不会对参数求值两次),也安全(与第二个选项不同)。

审查请求:

在第三个实现中,我已经(a) +(b)作为_通用,这将在有符号整数溢出时调用未定义的行为。我该如何避免?

将这3个实现合并到一个具有以下结构的源文件中是否是一个好主意:

如果定义了__GNUC__或__clang__,请使用复合表达式语句。否则使用C11的_Generic。无条件地使用带有注释的MIN/MAX来警告副作用。也许将前面的名称命名为MIN_SAFE/MIN_MAX。

? 你看到代码中的其他问题了吗,或者我忽略的实现之间的差异了吗?


后续行动:

\$\端组\$
6
  • \$\开始组\$ (a)类型可以代替汽车也是。 \$\端组\$
    – 哈里斯
    评论 5月26日18:01
  • 1
    \$\开始组\$ 使用_通用((a)+(b),类型永远不会小于整数. \$\端组\$ 评论 5月27日21:14
  • \$\开始组\$ @chux-ReinstateMonica是的,整数提升规则。我看不出保留3的用处烧焦类型和带符号短int无符号短整型然后。 \$\端组\$
    – 哈里斯
    评论 5月27日23:06
  • \$\开始组\$ 哈里斯,除了失踪带符号字符,代码也丢失_布尔从灵长类动物类型列表中0*(a)+0*(b),这也可以忽略。 \$\端组\$ 评论 5月28日0:23
  • 1
    \$\开始组\$ 我想起了其他一些类似的帖子,并在这里错误地应用了它。 \$\端组\$ 评论 5月28日1:31

4个答案4

重置为默认值
4
\$\开始组\$

让我们疯狂起来并生成N个宏和N*N个函数。

#定义MAX_SC(a,b)_通用(b)\带符号字符:max_scsc\无符号字符:max_scuc\//许多其他人长双精度:max_scld)((a),(b))#定义MAX_UC(a,b)_Generic(b)\带符号字符:max_ucsc\无符号字符:max_ucuc\//许多其他人长双精度:max_ucld)((a),(b))//其他许多MAX_。。(a、b)#定义MAX(a,b)_Generic(a)\签名字符:MAX_SC\无符号字符:MAX_UC\//许多其他人长双精度:MAX_LD)((a),(b))

现在最大INT最小值,UINT最大值UINT最大值
和的最小值INT最小值,UINT最大值内景_分钟.

无符号max_uii(无符号a,int b){返回b<0?a:(a>(无符号)b?a:(无符号)b);}int min_uii(无符号a,int b){返回(a>INT_MAX)?b:(int)a<b?(int)a:b);}混合FP和整数类型仍然是一个问题。
\$\端组\$
7
  • \$\开始组\$ 可能不支持FP,因为fmax()/fmin()是否存在? \$\端组\$
    – 哈里斯
    评论 5月27日21:25
  • \$\开始组\$ @Harith这是一个类型问题:例如:最小值-0x37777777,在f之后(-powf(2,31),0)? 内景最小+1,但那不是确切地可表示为浮动.注意“fmax()/fmin()自C99以来一直存在。 \$\端组\$ 评论 5月27日21:36
  • \$\开始组\$ 14个宏和196个函数最大值(),14个宏和196个宏最小值()不,我会回到可爱的单句。 \$\端组\$
    – 哈里斯
    评论 5月28日13:25
  • \$\开始组\$ @Harith Mixed类型更窄整数可以使用最大_i_i()。对于混合类型,如int,无符号,的_通用()无法解析并导致编译器故障。这种故障可能比最大值(-1,UINTMAX-1u)导致UINTMAX公司。使用此方法仅约8最大_()需要8个宏函数。 \$\端组\$ 评论 5月28日17:10
  • 1
    \$\开始组\$ @Harith我们可以用_通用((b)+0)消除对字符,有符号字符,无符号字符,有标记短字符,无标记短字符案例。 \$\端组\$ 评论 6月4日0:10
7
\$\开始组\$

复合表达式语句将限制x个,但仍可能与替换的表达式中出现的变量名冲突b条例如,如果最大值()如下所示:

最大值(x++,y+1);

复合语句表达式的第一条语句将为:

自动x=(x++);

第二个语句将变为:

自动y=(y+1);

GCC的文档建议在标识符前面加下划线,以避免这些冲突。虽然我不支持,因为以下划线为前缀的标识符是保留的。

你可以使用临时雇员/临时后缀或前缀,或类似的东西。


编辑:我发现了一种更好的生成唯一名称的机制:

我们将使用C预处理器来帮助我们,并且cdecl 3.0版,它支持几乎所有的C和C++标准和扩展,以扩展宏:

美元cdeclcdecl>#define CONCAT2_EXPAND(a,b)a##bcdecl>#define CONCAT2(a,b)CONCAT2_EXPAND(a,b)cdecl>#define UNIQUE_NAME(前缀)CONCAT2(CONCAT2,前缀,_),__LINE__)cdecl>展开UNIQUE_NAME(var)UNIQUE_NAME(var)=>CONCAT2(CONCAT2,前缀,_),__LINE__)|前缀=>变量UNIQUE_NAME(var)=>CONCAT2(CONCAT2,var,_),__LINE__)|CONCAT2(CONCAT2(var,_),__LINE__)=>CONCAT2_EXPAND(a,b)||a=>CONCAT2(变量,_)||CONCAT2(var,_)=>CONCAT2_EXPAND(a,b)|||a=>变量|||b=>_||CONCAT2(var,_)=>CONCAT2_EXPAND(var、_)||||CONCAT2_EXPAND(变量,_)=>a##b|||CONCAT2_EXPAND(var,_)=>var##_||||CONCAT2_EXPAND(变量,_)=>变量_|||CONCAT2(var,_)=>var_||a=>变量_||b=>__LINE__||__LINE__=>42|| b=>42|CONCAT2(var_,42)=>CONCAT2_EXPAND(var_、42)||CONCAT2_EXPAND(var_,42)=>a##b||CONCAT2_EXPAND(var_,42)=>var_##42||CONCAT2_EXPAND(var_,42)=>var_42|CONCAT2(变量_,42)=>变量_42UNIQUE_NAME(变量)=>变量_42cdecl>

CONCAT2()简单地串联其参数。CONCAT2_扩展()是必需的,因为##运算符不展开其操作数。唯一名称()将传递给它的前缀与当前行号连接起来。

这个扩大上面的命令显示了整个工作过程(实际输出非常丰富多彩。不幸的是,我无法在这里复制它)。这可能会生成一个非常独特的标识符。

我在找到这个方便的C/C++预处理器宏。这个博客是由写cdecl第3版的同一个人写的。cdecl的代码库也充分利用了这些宏。有关更多预处理器技巧,请参阅博客。

这些类似函数的宏都不能处理混合类型:

如果最大值()被传递给签署类型和未签名的类型签署类型很可能会转换为非常大的未签名的类型。的当前3个版本最大值()只适用于具有相同标志的类型。

对于长的整数,很好。对于未签名的无符号长,很好。

对于整数未签名的,不太好。对于整数无符号长,不太好。

溢流不是问题:

正如@Jonathan Leffer在关于StackOverflow问题的评论中所说:了解C11_通用:

请注意§6.5.1.1一般选择¶3表示:泛型选择的控制表达式未求值。。因为它没有被计算,所以它不会溢出。这很有道理。控制表达式在编译时确定类型;溢出发生在运行时。

所以(a) +(b)是正确的,除了@chux在评论中提到:

使用_泛型(((a)+(b),类型永远不会小于整数.


此解决方案不适用于MSVC:

根据C标准6.2.5.15,http://www.open-std.org/jtc1/sc22/wg14/www/docs/n869/n869.pdf.gz

三种类型烧焦,带符号字符,以及无符号字符是集体的称为字符类型。实施应明确烧焦具有与以下两者相同的范围、表示和行为带符号字符无符号字符. 32)

图表_最小值,定义于<限值.h>,将具有以下值之一0SCHAR_MIN公司,这可以用来区分这两个选项。无论做出何种选择,烧焦是与其他两个都不兼容。

但毫不奇怪,MSVC定义烧焦带符号字符作为相同类型,这意味着在_通用表达式,两者烧焦带符号字符应兼容。我认为在这种情况下需要进行诊断。请参见:_通用 烧焦,带符号字符,无符号字符在C11中不明显。该问题已在近4年前报告给微软,尚未修复,可能也不会修复。

为了避免这个问题,最简单的解决方案是使用原始的+ 0隐式提升为整数,而不检查任何烧焦类型。

\$\端组\$
9
  • \$\开始组\$ 为了避免宏中的冲突,我倾向于在它们后面加下划线和随机选择的字母数字后缀。冗长,但当生活给你柠檬。。。 \$\端组\$ 评论 5月27日7:38
  • \$\开始组\$ 如果剪辑听起来更像是一种宣传,我很抱歉,我刚刚变得非常喜欢这个节目 \$\端组\$
    – 哈里斯
    评论 6月6日17:24
  • 1
    \$\开始组\$ 不过,只有在前缀是user-facing的情况下才会这样。如果唯一名称在宏中使用,并且那个宏不公开前缀(但选择一个),然后在同一行上使用宏两次(可能是嵌套的,如最大值(a,最大值(b,c)))将导致不同的变量以相同的唯一名称结尾。 \$\端组\$ 评论 6月7日10:09
  • 1
    \$\开始组\$ 我没有那么远见,更多。。。经验丰富:“(我想不出其他什么了,我主要使用C++,所以我能够主要地通常不要使用宏,只需“不得不”使用它们来实现适当的日志记录和自省。 \$\端组\$ 评论 6月7日16:09
  • 1
    \$\开始组\$ 嗯,这很有趣。我没有意识到这是C和C++之间的分歧点。C代码仍有被包含在C++项目中的风险,因此对于小函数我建议内联函数,但对于纯C项目来说,它确实让事情变得更容易。 \$\端组\$ 评论 6月8日9:31
4
\$\开始组\$

UB公司

(a) +(b)

这将在有符号整数溢出时调用未定义的行为。

当然,整数附加可能溢出。

但我们的行动没有,对吧?就像所有的按位操作一样。异或看起来是个不错的候选人。

\$\端组\$
5
  • 1
    \$\开始组\$ 你说得对{}将确保局部变量仅在该范围内可见。但还有一个问题。在被替换的表达式中,仍可能与变量名冲突b条例如,如果我打电话给最大值()这样地:最大值(x++,y+1),第一条语句将变成:自动x=(x++);,第二条语句将变为:自动y=(y+1);.GCC的文档建议在标识符前面加下划线以避免这些冲突。 \$\端组\$
    – 哈里斯
    评论 5月26日21:04
  • \$\开始组\$ 是的,这是宏观扩张的一般问题。scheme/lisp社区将其称为卫生宏,并有几种寻址方法。其中包括特殊的解析器语法,以及(gensym)这就产生了一个全新的有保证的独特符号,用于扩展。 \$\端组\$
    – J_小时
    评论 5月26日21:13
  • \$\开始组\$ 按位操作不适用于浮点。 \$\端组\$ 评论 5月27日15:01
  • \$\开始组\$ 而不是(a) +(b),也许吧0*(a)+0*(b)获取通用类型并避免溢出? \$\端组\$ 评论 5月27日21:02
  • 4
    \$\开始组\$ 乘以零很聪明,是的,那么做。 \$\端组\$
    – J_小时
    评论 5月27日21:03
\$\开始组\$

浮点NaN错误

这个a>b?甲:乙检查将在以下情况下失败b条为NaN且不是,因为与NaN比较总是错误的。预期的行为是返回.使用fmaximum_num(最大值_数值),fmaximum_numffmaximum_numl对于这些类型。(与fmax(最大值)系列,这些定义用于正确处理NaN以及+0和-0之间的比较。)

在C23之前,理论上还有另一个问题可以提出-0的符号和大小表示。在C23中,所有有符号的积分类型都是二元补码。

\$\端组\$
  • \$\开始组\$ “因为与NaN比较总是错误的。”-->这是一个IEEE规范,C没有义务遵循它,尽管这种结果很常见。“当b是NaN而a不是时,a>b?a:b检查将失败,”在C中可能失败或通过。 \$\端组\$ 评论 5月27日21:46
  • \$\开始组\$ “预期的行为是返回”-->C2X在fmax()什么时候b条是一个安静的NAN,当两者都出现a、 b条是安静的,但不同的NAN。它也很安静fmax(+0.0,-0,0)fmax(-0.0,+0,0)-其中一个被退回?我没有看到C指定哪个。 \$\端组\$ 评论 5月27日21:49
  • \$\开始组\$ @chux-ReinstateMonica查了一下,在C23中,有一个单独的fmaximum_num(最大值_数值)指定+0.0>-0.0以及数字和NaN之间的任何比较返回数字的函数系列。 \$\端组\$ 评论 5月28日12:10

你的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.