标准C中有哪些限制?

28

C标准谈到了“约束条件”,例如ISO/IEC 9899:201x定义了这个术语。

constraint
指定如何解释语言元素的限制,可以是语法上的或语义上的

在章节Conformance中说:

如果违反了出现在约束条件或运行时约束之外的“应该”或“不应该”的要求,则行为未定义。

在章节Environment的子章节Diagnostics中说:

如果预处理翻译单元或翻译单元包含任何语法规则或约束条件的违规行为,即使该行为也明确指定为未定义或实现定义,符合规范的实现也必须生成至少一个诊断消息(以实现定义的方式进行标识)。

因此,了解C中的约束条件非常重要,例如对于编译器编写者来判断何时需要诊断,或对于C程序员来说,可以期望诊断而不仅仅是未定义的行为。
现在,在标准文档中有很多标题为Constraints的部分,但我找不到确切的措辞来说明标准中的术语constraint究竟包括什么。

  • 约束条件是否涵盖所有标题为Constraints的部分中出现的内容?
  • 在那些部分之外声明的每个要求是否都不是约束条件?
  • 标准中是否有全面描述约束条件的说明,我可能错过了?

4
有趣的问题,标准中的措辞很有趣。如果字面理解,似乎在限制条件内违反“shall [not]”并不一定会导致未定义行为,但我认为它们的意思是这种情况总是会产生未定义行为,并且要求实现必须强制进行“限制条件”违反的诊断。 - Peter - Reinstate Monica
7个回答

12
约束条件是否仅限于标题为“Constraints”的部分中的所有内容?
根据n1570 3.8(对程序施加的限制,要求符合规范的实现在违反时发出编译时诊断消息),我认为是的。
那些不在那些部分之外声明的每个要求都不是约束条件吗?
根据3.8的意义,我认为是的,但有一个更为循环的原因:标准的结构相当正式。只要适用,似乎就有明确的“约束条件”部分。因此,我理解在定义上,任何不在“约束条件”部分中的东西,在3.8的意义上都不是约束条件。除了“Constraints”部分外,还有一些“shall”条款完全可以在编译时执行,下面列举了一些示例。它们通常位于相邻的“Semantics”部分中。我可能会忽略某些细微差别,以防止在一般情况下进行编译时检测(因此不能强制进行诊断),或者可能标准不完全一致。但是,我认为编译器可以简单地翻译一个违反的程序,因为要求不在“Constraints”部分中。
标准中是否有关于“约束条件”的综合描述?
我认为只有3.8。我尝试在下面探索这个术语,并且同意该定义令人不满意。
术语“constraint”

让我们从基础知识开始。你引用的3.8中关于“约束”(constraint)的定义令人惊讶地难以理解,至少在没有上下文的情况下(“语法或语义上的限制,通过该限制可以解释语言元素的表述方式”)。“Restriction”和“constraint”是同义词,因此改写并没有增加太多内容;而“exposition of language elements”指的是什么呢?Exposition是一个有几个意思的单词;让我们从Dictionary.com中摘取,“主要用于传达信息的书写或演讲”,并假设它们是指标准的标准。那么,这基本上意味着此标准中的“约束”是对此标准中所说内容的约束。哇,我想不到。

Constraints按照3.8的定义

实际上,只需检查标准中实际的约束部分,就可以看到它们列出了强加给符合要求的程序的编译时限制。这是有道理的,因为只有编译时约束可以在编译时进行检查。这些附加限制是不能用C语法表达的。1

Constraints部分之外的限制

约束部分之外的“shall”大多数用法对符合规范的实现强加限制。例如:“所有具有静态存储期的对象在程序启动之前必须初始化(设置为它们的初始值)”,这是符合实现的任务。

虽然在约束部分之外,还有一些“shall”条款对程序(而不是实现)强加了限制。我认为其中大多数属于3.18中提到的“调用库函数时对程序运行时的约束……”。它们似乎是运行时约束,无法在编译时通常被检测出来(因此诊断可能不是强制性的)。

以下是一些例子。

在6.5/7中,n1570详细描述了备受争议的别名规则:

一个对象的存储值只能由具有以下类型之一的lvalue表达式访问:

  • 与对象的有效类型兼容的类型
  • 与对象的有效类型兼容的已限定版本的类型,[...]
在6.5.16.1中,“简单赋值”:

如果在存储第一个对象的存储器的任何位置重叠读取另一个对象中存储的值,则重叠必须是精确的[...]。

其他示例涉及指针算术(6.5.6/8)。
“应该”子句可以在“约束”部分中 但是还有其他违反应在编译时检测到的shall语句,如果它们出现在相应的约束部分中,我不会感到惊讶。
  • 6.6/6,“整数常量表达式中的强制类型转换运算符只能将算术类型转换为整数类型”(在“语义”下);如果无法检测常量和强制类型转换的类型,那么您可以在编译时检测到什么?
  • 6.7/7,“如果标识符用于声明没有关联的对象,则该对象的类型必须在其声明符的结尾处完成”(在“语义”下)。对我而言,在代码某个时刻检测出类型是否完整似乎是基本的编译器任务。但是,我从未编写过C编译器。
还有更多示例,但正如我所说,我认为实现不需要诊断违规行为。成功通过编译器的违规程序只会公开未定义的行为。1 例如,我理解语法不涉及类型 - 它只有通用的“表达式”。因此,每个运算符都有一个 约束 部分,详细说明其参数可接受的类型。例如移位运算符: “每个操作数应具有整数类型。” 尝试将浮点数的位进行移位的程序违反了这个约束,实现必须发出诊断。


1
感谢您详细的回答,听起来很有说服力。您是否同意以下两个细节?1.由于6.3.2.2中的要求“_void表达式_(具有类型void的表达式)的(不存在的)值不得以任何方式使用,并且不得对此类表达式应用隐式或显式转换(除了void)”,出现在约束部分之外,因此赋值给void表达式的值不是约束违规,对吗?(因此不需要诊断,尽管任何合理质量的实现都会产生一个。) - Armali
考虑 7.7浮点类型的特征 <float.h> §2 宏定义、它们的含义以及对它们值的限制或约束在5.2.4.2.2中列出。 Keith Thompson在comp.std.c中提到:我认为7.7使用了“constraints”这个词的普通英语意义,而不是标准所定义的意义。(它可能不应该这样做。) - Armali
1
@Armali 我认为在第一种情况下,诊断不是强制性的(但是,如果没有诊断,我也会感到惊讶)。我不确定是否可以构造编译器无法看到的情况,例如在不同翻译单元中具有冲突返回类型的函数声明(在调用TU中声明了返回int的void函数)。不确定是否符合要求。2.我认为Keith Thompson是正确的,标准中的措辞“(或限制)”也表明了这一点。 - Peter - Reinstate Monica
1
话虽如此,我对这一切并不完全有信心,我不会过多依赖标准的完全一致性。但是关于7.7和5.2.4.2.2.float.h:这些显然是对符合要求的实现强加的要求/限制,与3.8中对符合要求的程序施加的“约束”无关。(即那些不能用语法表达的限制,例如运算符的参数可以具有哪些类型——我理解C语法描述不涉及类型)。 - Peter - Reinstate Monica
1
约束只对严格符合 C 程序的限制。 - supercat
显示剩余4条评论

7
C委员会在回应缺陷报告#033时解决了这个问题。那份缺陷报告中的问题是:
“符合规范的实现是否需要诊断标准中''shall''和''shall not''语句的所有违规情况,即使这些语句出现在未标记为约束条件的部分?”
该缺陷报告的作者提出了几种可能的替代解释标准语言的方式。他列出的第二种替代方式如下(部分内容):
语法规则 列于标准的语法部分。 约束条件 则列于标准的约束条件部分。”
委员会回复中的一部分是:
“建议解释#2是正确的。”
我认为这相当全面地回答了您的问题,但只为了更直接地回答您的问题,请看以下回答:
“约束”是指明确标记为“约束”的部分中提出的要求。在这种部分之外提出的任何要求都不是约束。
据我所知,标准本身并没有包含有关什么是或不是约束的更具体说明,但链接的缺陷报告中有相关说明。

2
约束条件是否就是标题为“约束条件”的部分内容?
似乎大多数情况是这样的(有一些例外,比如在某个约束条件部分中说明“递增等同于加1”)。
除了那些部分以外提到的所有要求都不是约束条件吗?
我没有看到在那些部分以外的“约束条件”。
标准中是否有关于约束条件的全面描述,我错过了吗?
可能没有。如果有权威性的描述,它会出现在标准中,很可能是“约束条件”部分,并明确提到这些都是“约束条件”。
我的理解是,应该这样解释第三章:使用定义术语时,应按照该部分中定义的含义进行理解。特别是在任何使用“约束条件”一词的地方,都应根据您的第一个引用理解它。
你的第二个引用也不例外。在约束条件术语的定义中指出,没有要求显式地将约束条件称为约束条件。这意味着您必须确定它是否是一种限制。
但是,似乎有很多“必须”和“不得”的例子可以被视为这样的限制,而不必明确称之为约束条件。这将使所有“必须”和“不得”的发生强制执行或禁止某些实现行为,如果不满足这些要求,那么该行为可能是未定义的(因为您正在使用不符合标准的实现)。
看起来所有符合“约束条件”定义的内容都出现在“约束条件”部分中,并且在“约束条件”部分中的所有内容似乎都是“约束条件”。

感谢您的回答。您说“我没有看到除了那些部分以外的约束条件”。但是请考虑例如7.7浮点类型的特性<float.h> §2 _列出了宏、它们的含义以及对其值的约束(或限制)_,这表明5.2.4.2.2包含约束条件,而在那里没有提到constraint一词。我的问题是,如果我看到一个没有命名的约束条件,我是否能够认识到它以及如何认识到它。 - Armali

1
“Constraints”一节中提到的所有句法和语义限制都是约束条件。例如,对于“常量表达式”的约束条件(C11-6.6/3):
“除非它们包含在不被求值的子表达式中,否则常量表达式不得包含赋值、递增、递减、函数调用或逗号运算符115)。”
因此,常量表达式...
3 = 5;
10++;

显示约束违规。

请注意,在这种情况下,shall要求以及constraint都被违反了。

那么,在这些部分之外陈述的每个要求都不是约束吗?

对于符合标准的C语言来说,是的。对于整数常量表达式的shall要求(C11-6.6/6):

一个整数常量表达式117) 应该 具有整数类型[...]

例如,非变长数组的大小需要整数常量表达式。因此,

int arr[5+1.5];

违反了“应该”要求。表达式类型5+1.5不是整数类型。这个“应该”要求超出了限制。
需要注意的是,“应该”要求也可能是一种限制。

1
也许是因为最后一句话没有太多意义。问题是关于标准,而不是任何(可能不符合规范的)实现或甚至程序。[否则,我认为您正确地指出,在n1570中,所有3.8中所述的约束都在明确标记为“约束”的部分内。] - Peter - Reinstate Monica
1
OP的问题是“在名为“ISO C标准”的书中出现的每个要求是否都在标记为“约束条件”的那些部分之外,不是约束条件?”换句话说,标准中标记为“约束条件”的部分的内容是否全部且仅限于标准中的所有约束条件。你的答案“对于符合标准的C语言,是的”不适用。我们谈论的不是C语言,而是一本特定的书。答案只能是“是”或“否”,或者可能是“我不知道”。(哦,我也没有投反对票,只是试图提供帮助。) - Peter - Reinstate Monica
哈哈,如果你想的话,它可能是标准确认,但它不能成为标准符合。它就是该死的标准。 - Peter - Reinstate Monica
@Armali; 因为它说“不得以任何方式使用”,这是一种限制。 - haccks
此外,您自己回答了“是”的问题“在这些部分之外陈述的每个要求都不是约束条件吗?” - Armali
显示剩余12条评论

1
在我的需求工程工作中,“约束”和“要求”这两个词具有不同的范围。对于标准来说,明确定义这些词语是很重要的。我在标准中搜索了“约束”一词,似乎可以得出以下结论:
一个 约束 是指标准描述的行为的输入(前置条件)或输出(后置条件)的限制。对于输入,它意味着输入必须符合约束 (例如,argc 必须为正数)。对于输出,则意味着它必须满足约束,以便标准的任何后续单元都有一个良好定义的输入(它的前置条件)。 要求 是标准部分行为规范的一部分。“应当”是对所需内容的正面描述;“不应”通常是限制,但不是一个约束——它可能参与满足其输出的约束。
约束和要求可以被视为“外部接口”(约束)和“系统行为/处理”(要求)。

Shall通常表示要求(没有“shall”的短语因此不是要求)。在约束中使用“shall”则用于定义输入或输出(例如argc应为正数),或指定有关验证约束的行为(例如,“...应给出诊断消息”)。

严格来说,用于指定验证输入约束行为的“shall”不应列在约束部分(不应列在接口规范中),而应列在处理部分(行为部分)。

请注意,无法验证输出约束,因为输出应符合规范;只有下一个单元可以检查这些约束是否在其输入约束中。

这可能是个人观点,但似乎符合标准中这些词语的用法。


感谢您的回答。这似乎是一个连贯的观点,但恐怕它并不完全符合标准。例如,您说“对于输入,它意味着它可以假定约束已满足...但可能会检查约束(它不必这样做)”,而标准则表示“符合实现应生成至少一个诊断消息...如果预处理翻译单元或翻译单元包含任何语法规则或约束的违规”(因此必须检查)。 - Armali
此外,它还谈到了一个“应该”或“不应该”的要求,这个要求出现在约束条件之外,这让我想到一个要求可以很好地出现在约束条件内,这听起来像是与您所描述的“约束条件”一词不同的含义。 - Armali
注意:“argc应为正数”是一个有问题的例子,“argc的值应为非负数。”C11 5.1.2.2.1 2 - 它可以是0。 - chux - Reinstate Monica

0
约束
指语言元素的表达方式应该如何解释的限制,无论是句法还是语义上的。这意味着C标准设置的程序逻辑或语法的每个明确限制都是一种约束。这包括句法约束(例如,块必须以“;”结尾)和语义约束(例如,在初始化变量之前不得使用变量),基本上是任何句法上或语义上不允许或定义为不允许(未定义行为)的事情。
“那些部分之外陈述的所有要求都不是约束吗?”
我认为C语言编程的所有明确要求都属于句法或语义约束。
“标准中是否有关于约束的全面描述我错过了吗?”
据我所知,没有。

1
仅仅因为一个约束是一种限制,这是否意味着每个限制都是一个约束? - Columbo
@Columbo 我认为每个明确的限制都是一种约束。你有反例吗? - Magisch
感谢您的回答(我没有对其进行投票)。这似乎是一个合法的观点。然而,也有相反的观点,例如“所有约束都在标准中明确定义,在清晰标记的段落中”。(https://groups.google.com/d/msg/comp.std.c/HRvo1XRmmuc/jBDtqfpFlTkJ) - Armali
@Armali 语言律师经常是这样的,只有在边缘案例中才显得重要。 - Magisch

0

标准中约束的目的是指定符合实现需要发出诊断的条件,或者允许实现以与否则规定的行为相反的方式处理程序,在这种情况下,这样做可能比否则规定的行为更有用。尽管严格符合C程序不允许违反约束(违反约束的程序不是严格符合C程序),但对于旨在符合而不是严格符合的程序,不存在此类限制。

C标准是在多个重叠派别之间达成妥协的结果,包括:

  1. 那些认为它应该阻止程序员编写不能在所有平台上互换使用的代码
  2. 那些认为它应该允许针对已知平台的程序员利用所有这些平台都支持的功能,即使这些功能在所有平台上都无法支持
  3. 那些认为编译器应该被允许诊断构造和操作,这些构造和操作更容易因意外而不是故意而执行
  4. 那些认为它应该允许程序员做一些看起来错误的事情,比如执行地址计算,但如果按照规定精确执行,将产生程序员期望的对象的地址。
为了在这些团体之间达成共识,标准对于严格符合的 C 程序中可以做什么进行了限制,但同时也将符合的 C 程序的定义写得足够宽泛,以至于几乎没有有用的程序会被标记为不符合,无论它们依赖于多么晦涩的扩展。如果一个源代码结构违反了可诊断的约束条件,但是实现的客户仍然会发现它有用,那么实现可以输出一个诊断信息,其客户可以忽略(即使是一个无条件的:“警告:此实现不会输出其作者认为愚蠢的诊断信息,除了这个之外”也足够了),这样每个人都可以继续生活。

但是,“严格符合规范的程序应该仅使用国际标准中指定的语言和库特性”本身就是一个约束之外的要求。似乎编译器可以将每个程序都视为“严格符合规范的程序”,并且由于它违反了这个“应该”,因此声明该程序具有未定义行为,然后执行编译器喜欢的任何操作。 - Tuff Contender
@TuffContender:一个符合但无用的编译器可以说:“如果这个程序与一个特定的无用程序匹配,该程序名义上会超出翻译限制,那么就像那个程序一样行事,否则就以一种使结果与那个程序无法区分的方式炸掉堆栈”,在 as-if 规则下,它甚至不需要查看源代码就可以简单地像无用程序一样运行。从严格规范的角度来看,标准确实没有对除了严格符合规范的程序之外的任何要求。 - supercat
这是C标准措辞的问题吗?在C++草案中,“shall”意味着“必须”,并且“行为”的范围在程序中受到限制。我无法想象C标准中规定的实现的未定义行为是什么。例如,“每个枚举类型...应能够表示枚举成员的所有值”,标准允许实现违反这个“应该”,并将其视为未定义行为,这太奇怪了。我认为标准应该专注于符合规范的实现,并提及符合或不符合规范的程序。 - Tuff Contender
@TuffContender:我认为关于标准是否应该指定一个完整的有用语言,或者一组核心语言特性,供不同平台和目的的实现适当扩展,从来没有达成共识。而且,据我所知,也没有就标准的哪些方面应该是规范性的,哪些部分只是指南达成共识。除非标准的作者能就这些问题达成共识,否则一个有用且有意义的规范标准将是不可能的。 - supercat
@TuffContender:在C标准中最有用的部分应该是用强烈建议替换大多数实现要求,但增加一个要求,即偏离推荐实践的实现必须记录任何这样的偏差。 - supercat
@TuffContender:顺便说一句,除非事情发生了变化,C++标准明确地没有为程序定义符合性类别。相反,似乎对程序的限制仅用于限制实现必须有意义地行为的情况范围。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接