c-索引调用未定义的行为



所以我的朋友对如何在程序的调用堆栈中创建列表有一个非常非常反常的想法。其想法是,如果您可以在递归调用中计算同一堆栈变量之间的偏移量,则可以访问调用堆栈中更靠上的任意元素。这听起来很困惑,所以我决定实施它,它确实有效,但当然警钟长鸣:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
// Reads all of stdin and prints it in reverse order
void read(char* last)
{
char current;
if (last==NULL)
{
// null terminator
current = '';
read(&current);
}
else
{
int ip = getchar();
if (ip==EOF)
{
// end of stdin found, loop back over the stack to find all read characters
// Calculate offset between the stack frames
int offset = &current - last;

for (char* c = last; *c != ''; c -= offset)
{
printf("%c", *c);
}
}
else
{
current = (char) ip;
read(&current);
}

}

}
int main(int c, char* argv[])
{
read(NULL);
return 0;
}

问题是:

  • 这是UB吗?如果是,为什么
  • 没有UB可以使用模拟器吗

在没有UB的情况下可以使用模拟器吗?

是的,您只需要显式地构建一个链表:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
struct chain {
struct chain *prev;
char    ch;
}
// Reads all of stdin and prints it in reverse order
void read(struct chain *last)
{
int ip = getchar();
if (ip==EOF)
{
// end of stdin found, loop back over the stack to find all read characters

for (struct chain *p = last; p; p = p->prev) {            {
printf("%c", p->ch);
}
}
else
{
struct chain current = { last, (char)ip };
read(&current);
}    
}
int main(int c, char* argv[])
{
read(NULL);
return 0;
}

但是,很有可能发生堆栈溢出。。。

这是UB吗?

是。

如果是,为什么?

因为它通过不相关的句柄访问内存。一般情况下,请参阅指针出处n2263。

因为标准并不能保证对象将被分配为相邻的,并且在递减的内存地址中是连续的。不能保证c指针的值是有效的。因为没有这样的保证,所以行为没有定义。

在没有UB的情况下可以使用模拟器吗?

否。

一些实现非常详细地记录了它们将如何布局堆栈帧,至少在使用某些选项调用时是这样[这些选项通常限制编译器将执行的优化类型]。如果这样的实现的行为与其文档一致,那么依赖于行为如文档所示的编译器的代码将是不可移植的,但可能在行为如所述的编译器上具有正确定义的行为。如果标准没有要求实施文件,但实施无论如何都这样做了,那么此类文件的准确性将是标准管辖范围之外的实施质量问题。一个一致的实现可以自由地记录它喜欢的几乎任何东西,无论它的行为是否与它记录的内容有任何关系。

请注意,就标准的编写方式而言,很少有情况下,一个原本符合要求的实施方案可能会对某个特定的非人为程序造成任何影响,从而使其不符合要求。实际上,编写一个程序是不可能的,因为它不能设计出一个低质量但符合要求的实现,当提供给它时,它会表现得毫无意义[事实上,我们可以设计一对这样的实现,这样两个实现都不能有意义地处理源文本]。

我认为,合理的建议是,在标准没有要求的情况下,质量实施只应规定他们将以某种方式处理某些构造,如果他们将以这种方式持续处理这些构造,即使标准不要求他们这样做这种方式,但有时会做其他事情,可能是符合要求的,但应该被认为是质量低劣的。虽然有时程序员为低质量的实现做出让步可能是有用的或必要的,但此类实现的维护者不应将此类让步视为对其实现质量的任何认可。

通过利用堆栈框架布局的详细知识可以执行的任务相对较少,而这些任务在不依赖这些知识的情况下无法做得更好。此外,依赖堆栈框架布局的代码可能只在相当小的一部分实现上有效地运行。尽管如此,这样的构造应该被期望在高质量的实现中有效地工作,这些实现以足够的细节记录它们的堆栈框架布局,以指定程序员所需要的一切。

最新更新