Fortran 是否有未定义的行为?
在C 和C++ 中,有许多操作会导致未定义的行为,即允许编译器做任何它想做的事情的情况。 示例包括在释放变量后使用变量、释放变量两次以及取消引用空指针。
Fortran 也有未定义的行为吗?我查看了一份规范草案,但没有找到任何内容。例如,在释放变量后使用变量是否一定会导致程序崩溃,还是可能默默地做错误的事情?
In C and C++, there many operations that cause undefined behavior, i.e. situations that allow the compiler to do anything it wants. Examples include using a variable after deallocating it, deallocating a variable twice and dereferencing null pointers.
Does Fortran also have undefined behavior? I had a look at a specification draft, but failed to find anything in there. For instance, is using a variable after its deallocation guaranteed to crash the program, or may it silently do the wrong thing?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

发布评论
评论(3)
Fortran 标准确实承认与 C 和 C++ 的“未定义行为”概念类似的概念。支持损坏的 Fortran 代码的人们经常将其表述为“编译器现在可能会引发第三次世界大战”或类似的说法。
Fortran 语言规范有两个一致性想法(参见 Fortran 2018, 4.2)。主要问题是程序必须是什么样子才能被视为 Fortran 程序。第二个是处理器必须执行哪些操作来响应提交的程序单元才能被视为 Fortran 处理器。
如果要求符合 Fortran 的处理器处理非 Fortran 程序的内容,该标准很少说明必须发生什么。有些诊断是必须提供的,但通常没有。
在“在释放变量后使用变量”的情况下,尝试执行此操作违反了定义 Fortran 程序的语言标准部分。然后,编译器可以在不违反 Fortran 标准的情况下“发动第三次世界大战”,因为 Fortran 标准并没有说它不能(或必须做其他事情)。
现在,我们如何查看Fortran标准文档并确定不完全Fortran程序是否具有特定的所需效果? 4.2 中的文本提到了编译器必须具有“检测和报告提交的程序单元内的使用情况的能力”的多种情况。如果你提出的计划没有达到其中任何一个目标,那么你就处于“未定义”的领域。
程序错误必须可报告的主要时间是在这种情况下
在提交的程序单元内使用编号语法规则或约束不允许的形式或关系
让我们任意考虑 Fortran 2018, 15.5.1C1523 (R1520) 过程引用的语法。我们看到类似“R1520”的东西:
R1520函数引用是过程指示符([actual-arg-spec-list])
和“C1523”:
C1523 (R1520)过程指示符应指定一个函数。
在我们列出以下内容之前:
过程指定符中的数据引用不应是未分配的可分配变量或未关联的指针。
在本例中,规则 R1520、编号约束 C1523(适用于该规则)和以下文本给出了对 Fortran 程序的约束。如果您提交的程序不符合这些要求,则它不是合格的 Fortran 程序。
要求处理此类不合格程序(该程序违反 R1520 或 C1523)的编译器必须能够检测到这一点(基于上述内容)。编译器不必抱怨或检测未编号文本的违规行为。可以假设它提供的任何程序都不会违反这种未编号的限制。
我在这里引用的这个(巧合)是禁止程序错误地使用先前释放的变量的一个示例。
如果处理器以“编译然后运行”的方式操作,则编号的规则/约束通常是可以“在编译时”评估的规则/约束。
未定义行为的另一个具体且重要的示例是使用变量而不首先给它一个值(“定义”它)。 Fortran 标准简单地说(Fortran 2018 9.2 p2):
仅当定义了变量时才允许引用。仅当指针与已定义的目标对象关联时,才允许对数据指针的引用。当 19.6.5 中描述的事件发生时,变量将被定义一个值。
这不是编号语法规则或约束,而是编译器允许假设已满足的程序的(重大)负担。
Fortran 和 C 之间的主要区别在于,后者由 Dennis Ritchie 设计,指定了许多在 FORTRAN 中不可能或错误的操作的行为,其方式可用于消除对 FORTRAN 中某些更多操作的需要。复杂的结构。
例如,在 Dennis Ritchie 的语言中,知道多维数组中元素总数的代码可以仅使用最后一个下标迭代整个数组。像这样的一段代码
double a[5][5],b[5][5];
void add_arrays()
{
int i;
for (i=0; i<25; i++)
a[0][i] += b[0][i];
}
不会被视为碰巧有效的错误代码,而是被视为利用了常见的 C 习惯用法的代码。 [请注意,在 FORTRAN 或 Fortran 中执行上述操作的惯用方法是简单地使用矩阵加法]。
到 C 标准编写时,实现可以至少通过三种方式处理函数,例如:
double test(int i)
{
a[1][0] = 1.0;
a[0][i] = 2.0;
return a[1][0];
}
以惯用的 C 方式,第二个赋值计算地址并写入它,从而允许以下可能性:写入可能会影响a[1][0]。
如上所述,但编译器假设 a[1][0] 不会受到第二次赋值的影响,因为它位于数组的不同行中。
进行明确的边界检查,如果
i
不在 0 到 4 的范围内,则会陷入困境。
上述每种解释都会使编译器更适合某些任务,而不太适合其他任务。该标准并未暗示上述任何解释均优于其他任何解释,而是将对 a[0][i]
的访问描述为对于 值而言“不可移植或错误” i在5到24的范围内;根据实现,在某些实现上,此类操作将是错误的,但在其他实现上,尽管它们是不可移植的,但它们将是正确的。
尽管可能会出现 Fortran 编译器以类似于 #2 的方式处理代码的情况(产生与顺序程序执行不一致的语义,但不会产生诊断),但我认为该语言不会有很多情况可以将此类操作视为某些实现是错误的,不可移植但其他实现是正确的。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
是的,确实如此。只是叫法不同而已。您可以做很多事情,并且会使您的代码不符合标准,为此,处理器(编译器)不需要诊断这种不符合性(当然,许多偏差必须被诊断)。通常,情况与 C 未定义行为类似(例如访问数组越界、有符号整数溢出……)。我们只是说该代码不符合标准,这意味着该标准没有规定此类代码的结果。此类代码并未涵盖,而是标准的,因此如果某些编译器(处理器)确实编译了它并且您确实运行了它,则可能会产生任何结果。
这与依赖处理器的行为不同,它是标准的并且仅依赖于实现。
只需在 StackOverflow 上搜索即可找到大量示例。就像 将相同的实体传递给具有不同意图的参数是否是未定义的行为? Fortran 和 MPI_Reduce 如何处理整数溢出?
这个答案只是回答了所提出的问题,但并不试图列出 Fortran 中可能发生的所有可能类型的 UB。
Yes, it has. It is just called differently. There are many things that you could do and will make your code not standard conforming, for which there is no requirement to for the processor (compiler) to diagnose such non-conformance (of course, many deviations must be diagnosed). Often the situations will be similar to C undefined-behaviour one (like accesing an array out-of-bounds, signed integer overflow,...). We just say that the code is not standard conforming, that means the standard does not prescribe the outcome of such a code. Such code is not covered but the standard and so anything can result if some compiler (processor) does compile it and you do run it.
That is different from processor dependent behaviour, that one is standard and just implementation dependent.
Just searching here at StackOverflow should give you plenty of examples. Like Is passing the same entity to arguments with different intent undefined behavior? How do Fortran and MPI_Reduce deal with integer overflow?
This answer just answers the question asked but does not attempt to list all possible kinds of UB that can happen in Fortran.