在过去的几年里,我并没有非常使用过C语言.当我今天读到这个问题时,我遇到了一些我不熟悉的C语法.
显然在C99中,以下语法有效:
void foo(int n) {
int values[n]; //Declare a variable length array
}
Run Code Online (Sandbox Code Playgroud)
这似乎是一个非常有用的功能.有没有关于将它添加到C++标准的讨论,如果是这样,为什么它被省略?
一些潜在的原因:
C++标准规定数组大小必须是常量表达式(8.3.4.1).
是的,当然我意识到在玩具示例中可以使用std::vector<int> values(m);,但这会从堆中分配内存而不是堆栈.如果我想要一个多维数组,如:
void foo(int x, int y, int z) {
int values[x][y][z]; // Declare a variable length array
}
Run Code Online (Sandbox Code Playgroud)
该vector版本变得很笨拙:
void foo(int x, int y, int z) {
vector< vector< vector<int> > > values( /* Really painful expression here. */);
}
Run Code Online (Sandbox Code Playgroud)
切片,行和列也可能遍布整个内存.
看一下comp.std.c++这个问题的讨论很明显,这个问题在争论的两个方面都有一些非常重要的名字引起争议.毫无疑问,a std::vector总是更好的解决方案.
编辑:这个问题并不是一个讨论未定义行为的(de)优点的论坛,但这就是它的变化.在任何情况下,这个关于假设的C编译器没有未定义行为的线程可能对那些认为这是一个重要主题的人更感兴趣.
当然,"未定义行为"的经典伪装例子是"鼻子恶魔" - 物理上是不可能的,无论C和C++标准允许什么.
因为C和C++社区倾向于强调未定义行为的不可预测性以及允许编译器在遇到未定义行为时使程序完全做任何事情的想法,所以我假设标准没有任何限制关于行为,以及未定义的行为.
[C++14: defns.undefined]:[..]允许的未定义行为包括完全忽略不可预测的结果,在转换或程序执行期间以环境特征(有或没有发出诊断消息)的文档方式执行,终止转换或执行(发布诊断信息).[..]
这实际上指定了一小组可能的选项:
我假设在大多数情况下,编译器选择忽略未定义的行为; 例如,当读取未初始化的内存时,可能是插入任何代码以确保一致行为的反优化.我认为陌生人类型的未定义行为(例如" 时间旅行 ")将属于第二类 - 但这需要记录这些行为并"环境特征"(所以我猜鼻腔恶魔只能由地狱计算机?).
我误解了这个定义吗?这些仅仅是可能构成未定义行为的例子,而不是一个全面的选项列表吗?"任何可能发生的事情"的说法仅仅意味着忽视这种情况的意外副作用吗?
编辑:两个小问题澄清:
我正在研究核心常量表达式*中允许的内容,这在C++标准草案的5.19 常量表达式第2段中有所描述:
条件表达式是核心常量表达式,除非它涉及以下之一作为潜在评估的子表达式(3.2),但是未评估的逻辑AND(5.14),逻辑OR(5.15)和条件(5.16)操作的子表达式不考虑[注意:重载的运算符调用函数.-end note]:
并列出随后的子弹中的排除项并包括(强调我的):
- 具有未定义行为的操作 [注意:包括,例如,有符号整数溢出(第5条),某些指针算术(5.7),除零(5.6)或某些移位操作(5.8) - 结束注释];
嗯?为什么常量表达式需要此子句来涵盖未定义的行为?常量表达式是否有一些特殊的东西需要未定义的行为才能在排除中进行特殊划分?
拥有这个条款是否给了我们没有它的任何优势或工具?
作为参考,这看起来像广义常量表达式提案的最新修订版.
C++标准配备的用于定义一个惊人的数不清楚1种,其意味着与细微的差别或多或少相同的行为.读到这个答案后,我注意到"该程序格式错误;无需诊断"的措辞.
实现定义与未指定的行为的不同之处在于前一种情况下的实现必须清楚地记录它正在做什么(在后一种情况下,它不需要),两者都是格式良好的.未定义的行为与未指定的行为不同,因为程序是错误的(1.3.13).
否则它们都有一个共同点,就是标准不会对实现的内容做出任何假设或要求.除1.4/8之外,其中声明实现可能具有不会改变格式良好的程序的行为的扩展,但根据标准是不正确的,并且实现必须诊断使用这些,但之后可以继续编译和执行不正当的计划.
一个病态的程序否则只能定义为没有很好地形成(太棒了!).甲合式程序,在另一方面,被定义为一个附着在语法和诊断的语义规则.因此,这意味着错误的程序是打破语法或语义规则(或两者)的程序.换句话说,一个不正确的程序实际上根本不应该编译(如何以任何有意义的方式翻译例如具有错误语法的程序?).
我倾向于认为错误这个词也意味着编译器应该使用错误消息中止构建(毕竟,错误表明存在错误),但1.3.13中的"注意"部分明确允许不同的东西,包括默默地忽略问题(并且编译器显然不会因为UB 而破坏构建,大多数甚至不会默认警告).
人们可能会进一步认为错误和不正确的形式是相同的,但如果情况或该词应该是什么意思,那么标准就不会详细说明.
此外,1.4表示
符合要求的实施应[...]接受并正确执行格式良好的程序
和
如果程序包含违反不需要诊断的规则,则不要求对该程序进行实施.
换句话说,符合要求的实现必须接受格式良好的程序,但它也可能接受形式错误的程序,甚至没有警告.但是,如果程序因为使用扩展而格式不正确.
第二段建议任何与"无需诊断"相关的内容意味着规范中没有要求,这意味着它大部分等同于"未定义的行为",除非没有提到错误.
因此,使用"形成不良;无需诊断"等措辞背后的意图是什么?
"无诊断"的存在表明它与未定义的行为完全相同(或大部分相同?).此外,由于实现定义和未指定的行为被定义为格式良好,因此它必须是不同的.
另一方面,由于格式错误的程序会破坏语法/语义规则,因此它实际上不应该编译.但是,与"不需要诊断"相结合意味着允许编译器在没有警告的情况下以静默方式退出,并且之后您将无法找到可执行文件.
"形成不良;无需诊断"和"未定义行为"之间是否存在差异,或者这只是同一事物的复杂同义词?
这似乎是一个初学者的问题,但我对编译器通常创建变量维数组的方式感兴趣,就像在下面的程序中一样.
#include<iostream>
int main(){
int n;
std::cin>>n;
int a[n];
}
Run Code Online (Sandbox Code Playgroud)
根据我的学习,在C中,所有初始化值都必须是常量,这样编译器就知道要在函数内保留多少内存,通常是通过减去堆栈指针来容纳数组所包含的元素数.
这对我来说很有意义.但是,我不太明白编译器如何处理上述程序,因为它似乎与G ++(MinGW)一起工作,但是在C,Microsoft的C++编译器中失败了.我怀疑GCC通过非标准扩展在堆上分配内存,但我不确定.
此外,微软的编译器并不因标准兼容而闻名,所以如果它对待上述程序的方式实际上可能出错,我也不会感到惊讶.