将C要求应用于未选定的_Generic案例 [英] Applying C requirements to unselected _Generic cases

查看:56
本文介绍了将C要求应用于未选定的_Generic案例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(请注意,这是一个语言律师问题.)

(Note this is a language-lawyer question.)

更新:我对问题0进行了修改.撰写本文时,我正在查看C 2018 6.5.2.2 6中的参数自变量类型规则,这些规则不在约束"部分中,因此可能被编译器忽略.我忽略了约束"部分中的6.5.2.2 2,因此需要编译器来诊断不匹配的类型.如果我注意到这一点,我就不会问0.

Update: I botched Question 0. When I wrote this, I was looking at the parameter-argument type rules in C 2018 6.5.2.2 6, which are not in a Constraints section and so may be neglected by a compiler. I overlooked 6.5.2.2 2, which is in a Constraints section, so a compiler is required to diagnose mismatched types. I would not have asked Question 0 if I had noticed this.

此问题中,我们所需的代码,例如:

In this question, we desire code such as:

int AddVersion0(int a, int b       ) { return a+b;   }
int AddVersion1(int a, int b, int c) { return a+b+c; }

typedef int (*TypeVersion0)(int, int);
typedef int (*TypeVersion1)(int, int, int);

#define Foo(f, a, b)    _Generic((f),  \
        TypeVersion0: (f)((a), (b)),   \
        TypeVersion1: (f)((a), (b), 0) \
    )

#include <stdio.h>

int main(void)
{
    printf("%d\n", Foo(AddVersion0, 3, 4));
    printf("%d\n", Foo(AddVersion1, 3, 4));
}

( Foo 已通过函数 f 进行了参数设置,以方便演示和分析.在原始情况下,这不是必需的.)

(Foo has been parameterized with a function f to facilitate demonstration and analysis. In the original context, this is not needed.)

使用默认开关,还可以选择添加 -std = c18 以及GCC 10.2和Apple Clang 11.0 拒绝此代码抱怨是错误,不是警告,一个参数太多函数调用( AddVersion0 在第一次使用 Foo 的第二种情况下),而对另一个函数调用( AddVersion1 ).

With default switches, optionally adding -std=c18, both GCC 10.2 and Apple Clang 11.0 reject this code complaining as an error, not a warning, there are too many arguments to one function call (AddVersion0 in the second case of the first use of Foo) and too few to another (AddVersion1 in the first case of the second).

如果通用选择的通用名称类型与控制表达式的类型兼容,则通用选择的结果表达式就是该通用关联中的表达式.

问题1

接下来,请考虑以下解决方法:

Question 1

Next, consider this workaround:

int AddVersion0(int a, int b       ) { return a+b;   }
int AddVersion1(int a, int b, int c) { return a+b+c; }

typedef int (*TypeVersion0)(int, int);
typedef int (*TypeVersion1)(int, int, int);

int NeverCalled();
#define Sanitize(Type, f)   _Generic((f), Type: (f), default: NeverCalled)
#define Foo(f, a, b)    _Generic((f),  \
        TypeVersion0: Sanitize(TypeVersion0, (f))((a), (b)),   \
        TypeVersion1: Sanitize(TypeVersion1, (f))((a), (b), 0) \
    )

#include <stdio.h>

int main(void)
{
    printf("%d\n", Foo(AddVersion0, 3, 4));
    printf("%d\n", Foo(AddVersion1, 3, 4));
}

GCC 10.2和Apple Clang 11.0都没有对此抱怨.

Neither GCC 10.2 nor Apple Clang 11.0 complain about this.

问题1::编译器能否有理由对此进行抱怨?由于未使用原型声明 NeverCalled ,因此C 2018 6.5.2.2 6不会说任何调用具有未定义的行为,除非函数被定义且其类型不包含原型和参数类型与参数类型不匹配.但是功能根本没有定义,所以条件是没有触发.

Question 1: Could a compiler have grounds to complain about this? As NeverCalled is not declared with a prototype, C 2018 6.5.2.2 6 does not say any call has undefined behavior unless the function is defined with a type that does not include a prototype and the argument types do not match the parameter types. But the function is not defined at all, so that condition is not triggered.

(我问编译器是否有投诉的理由,因为当然可以允许编译器投诉任何事情,这是不会阻止编译程序的警告,但问题是编译器是否可以推断出此方面的某些方面代码违反了C标准的某些方面.)

(I ask whether the compiler has grounds for complaint because of course a compiler is permitted to complain about anything, as a warning that does not prevent compiling the program, but the question is whether a compiler could deduce that some aspect of this code violates some aspect of the C standard.)

推荐答案

问题0 :此代码是否严格符合C标准,因此GCC和Clang拒绝它是错误的吗?

Question 0: Is this code strictly conforming to the C standard, so GCC and Clang are wrong to reject it?

否.

不仅不匹配在执行程序中从未评估过的案例,

Not only are the mismatched cases never evaluated in an executing program,

是否评估未选择的表达式无关紧要.根据标准中提供的语法,表达式仍然需要为 assignment-expressions .解开该产品以发现其如何应用于所讨论的代码,我们发现它行使了 postfix-expression 的选项之一(第6.5.2节).适用第6.5.2.2节,其中指定的语言限制包括

Whether unselected expressions are evaluated is not relevant. The expressions still need to be assignment-expressions according to the grammar presented in the standard. Unwinding that production to find how it applies to the code at issue, we find that it exercises one of the options for a postfix-expression (section 6.5.2). Section 6.5.2.2 applies, and among the language constraints it it specifies is that

如果表示被调用函数的表达式的类型为包括原型,论据的数量应与参数数量.每个参数的类型应使得其值可以分配给具有非限定版本的对象相应参数的类型.

If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters. Each argument shall have a type such that itsvalue may be assigned to an object with the unqualified version of the type of its corresponding parameter.

我不使用措辞表示被调用函数的表达式".表示约束仅在评估函数调用的情况下适用,而是作为在形式上引用与形式化 postfix-expression 对应的表达式的各种尴尬方式中的合理选择语法.

I do not take the wording "the expression that denotes the called function" to mean that the constraint applies only in the event that the function call is evaluated, but rather as a reasonable choice among the various awkward ways of referring to the expression corresponding to the postfix-expression production in the formal grammar.

他们实际上没有在处理 _Generic 之后存在,因为 _Generic 被定义为在C 2018 6.5.1.1中产生一个结果表达式",而不是结果值"

they effectively do not exist after the _Generic is processed because _Generic is defined to produce a "result expression," not a "result value," in C 2018 6.5.1.1

否,没有定义泛型选择来生成表达式.术语结果表达"是指术语结果表达".在第6.5.1.1节中定义为根据控制表达式的类型选择的内容,但其含义是根据第6.5.1.1/4节:确定通用选择的评估结果:

No, a generic selection is not defined to produce an expression. The term "result expression" is defined in section 6.5.1.1 as the one that is selected based on the type of the controlling expression, but its significance is in determining the result of evaluating the generic selection, per paragraph 6.5.1.1/4:

通用选择的类型和值与其结果表达式.它是左值,函数指示符或如果void的结果表达式分别是一个左值,则为void表达式,一个功能指示符或一个空表达式.

The type and value of a generic selection are identical to those of its result expression. It is an lvalue, a function designator, or a void expression if its result expression is, respectively, an lvalue, a function designator, or a void expression.

作为表达式,结果表达式本身并不是评估通用选择的结果.

The result expression is not itself, as an expression, the result of evaluating the generic selection.

尽管任何给定泛型选择表达式的结果表达式都是静态已知的,但这不会使该表达式与其他关联(永远不会被评估)不能成为程序的一部分.这是因为通用选择是语言本身的一部分,而不是预处理功能.

Although the result expression for any given generic selection expression is known statically, that does not make the expressions with other associations, which will never be evaluated, fail to be part of the program. This is a consequence of generic selection being part of the language proper, as opposed to being a preprocessing feature.

  1. GCC和Clang会将运行时约束应用于不属于程序一部分的函数调用.

我不接受预处理后源代码中存在的表达式由于死代码而不能成为程序的一部分.我不知道有任何重要的观点认为,其他形式的无效代码无法成为程序的一部分".从某种意义上说,它们被免于遵守语言限制.

I do not accept that expressions present in the source after preprocessing fail to be part of the program on account of being dead code. I am not aware of any significant sentiment that other forms of dead code fail to be "part of the program" in the sense that they are excused from conforming to language constraints.

另一方面,尽管编译器有义务诊断约束违例,但编译器无义务拒绝包含约束的程序.在这种情况下,我会发出关于未选择的表达式的参数计数不匹配的警告,这是一种非常合理的行为,但是仍然可以接受代码,因为不匹配的原因并不特殊.

On the other hand, although compilers are obligated to diagnose constraint violations, they are not obligated to reject programs containing them. I would consider it an eminently reasonable behavior in this case to emit warnings about the argument count mismatch for the unselected expressions, but accept the code nevertheless, since the mismatches are of no particular account.

问题1 :编译器能否有理由对此进行抱怨?由于未使用原型声明 NeverCalled ,因此C 2018 6.5.2.2 6会进行声明不要说任何呼叫具有未定义的行为,除非该函数已已定义不包含原型和参数类型的类型与参数类型不匹配.但是函数没有定义在全部,因此不会触发该条件.

Question 1: Could a compiler have grounds to complain about this? As NeverCalled is not declared with a prototype, C 2018 6.5.2.2 6 does not say any call has undefined behavior unless the function is defined with a type that does not include a prototype and the argument types do not match the parameter types. But the function is not defined at all, so that condition is not triggered.

我同意,由于未定义甚至未使用原型声明 NeverCalled ,因此它甚至无法为违反第6.5.2.2/2节中的约束提供依据.由于内部泛型选择的计算结果是函数指定符(允许),而不是函数调用,并且由于最终不会生成对 NeverCalled()的调用,因此我也看不到甚至在没有范围内原型的情况下调用函数的警告的真实依据.我可以想象,如果编译器的表达式分析不成功,反而会发出这样的警告,但是我认为这样的警告是虚假的.

I agree that since NeverCalled is not defined or even declared with a prototype, it provides no basis for even a potential violation of the constraints in section 6.5.2.2/2. Since the inner generic selection evaluates to a function designator (which is allowed), not a function call, and since that does not end up producing a call to NeverCalled(), I also see no bona fide basis for even a warning about calling a function without an in-scope prototype. I could imagine a compiler issuing such a warning anyway if its expression analysis were not up to snuff, but I would consider such a warning spurious.

更新:

但是,在回顾了@Artyer的回答之后,我确信第二个示例表现出未定义的行为,这至少构成了意识到它要抱怨的编译器的合理借口.有关规定来自第6.9/5段:

However, having reviewed @Artyer's answer, I am convinced that the second example exhibits undefined behavior, which constitutes at least a reasonable pretext for a compiler that recognizes it to complain. The relevant provision is from paragraph 6.9/5:

如果在外部链接中使用了通过外部链接声明的标识符表达式(不是作为操作数的一部分 sizeof _Alignof 运算符,其结果为整数常量),在整个程序的某处,应该只有一个外部标识符的定义

If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier

由外部标识符 NeverCalled 表示的函数指示符不用于形成函数调用表达式,这不在该要求的范围之内,也没有任何其他的排除依据.因此,如果程序中没有该功能的外部定义,则该程序违反了应".约束之外的需求,因此行为也不确定.这将构成编译器投诉的理由,尽管我当然希望没有人会基于这些理由拒绝该程序.

That the function designator represented by the external identifier NeverCalled is not used to form a function call expression is not among the exclusions from that requirement, nor is there any other basis for an exclusion. Therefore, if there is no external definition of that function in the program then the program violates a "shall" requirement outside a constraint, and so has undefined behavior. That would constitute grounds for a compiler to complain, though I certainly hope none would reject the program on those grounds.

当然,您可以提供 NeverCalled()的定义来解决此问题.该定义不必在泛型选择出现的任何地方都在范围内,并且由于永远不会调用该函数,因此尤其是永远不要使用错误的数量或类型的参数来调用该函数.

Of course, you could just provide a definition of NeverCalled() to work around that. The definition does not need to be in scope anywhere that the generic selection appears, and since the function is never called, it is in particular never called with the wrong number or type of arguments.

这篇关于将C要求应用于未选定的_Generic案例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆