为什么不可能构建一个可以确定C++函数是否会改变特定变量值的编译器?

Cri*_*ter 104 c++ compiler-construction

我在一本书中读到这一行:

实际上,构建一个能够实际确定C++函数是否会改变特定变量值的编译器是不可能的.

该段讨论了在检查const-ness时编译器保守的原因.

为什么不可能构建这样的编译器?

编译器总是可以检查是否重新分配了一个变量,是否正在调用一个非const函数,或者它是否作为非const参数传入...

Cal*_*leb 139

为什么不可能构建这样的编译器?

出于同样的原因,您无法编写将确定任何给定程序是否将终止的程序.这被称为暂停问题,它是那些不可计算的东西之一.

为了清楚起见,您可以编写一个编译器,在某些情况下可以确定某个函数确实更改了该变量,但您不能编写一个可靠地告诉您该函数将会或不会更改该变量(或停止)的函数.每个可能的功能.

这是一个简单的例子:

void foo() {
    if (bar() == 0) this->a = 1;
}
Run Code Online (Sandbox Code Playgroud)

编译器如何通过查看代码来确定是否foo会改变a?它是否确实取决于函数外部的条件,即执行bar.暂停问题不可计算的证据不止于此,但在链接的维基百科文章(以及每个计算理论教科书)中已经很好地解释了,所以我不会试图在这里正确解释它.

  • @mrsoltys,量子计算机对于某些问题"只"指数级更快,它们无法解决不可判定的问题. (47认同)
  • @ThorbjørnRavnAndersen:好的,假设我正在执行一个程序.我究竟如何确定它是否会终止? (9认同)
  • @mrsoltys那些指数级复杂的算法(如因子分解)对于量子计算机来说是完美的,但是停止问题是一个逻辑上的困境,无论你拥有什么样的"计算机",它都是不可计算的. (8认同)
  • @ThorbjørnRavnAndersen但是如果你*实际上*执行程序,并且它没有终止(例如一个无限循环),你将永远不会发现它没有终止...你只是继续执行一个步骤,因为它可能是最后一个...... (8认同)
  • @mrsoltys,只是为了聪明,是的,它会改变.不幸的是,这意味着算法终止并仍在运行,遗憾的是,如果没有直接观察,你无法分辨哪个算法会影响实际状态. (7认同)
  • @ThorbjørnRavnAndersen:你也无法确定*IF*它会终止*通过*运行它直到它终止,因为 - 如果它没有呢?换句话说 - 你不能编写一个程序来检查其他程序,如果它停止则保证打印"Halts",如果不停止则保证"不停止". (5认同)
  • 基本上,原始问题中的陈述确实_not_表示编译器,当被问及"此函数是否修改X?"时 只能回答"我不知道".但是,_does_意味着编译器可以将功能分为"是","否"和"我不知道"类别,但对于所有可能功能的集合,"我不知道"类别永远不会是空的; 总会有_some_函数,它无法分辨. (4认同)
  • 我不认为你必须调用停止问题来解释这个限制.仅仅说一个函数可以根据运行时条件修改一个变量是不够的,这在编译时是不可知的?例如`foo(int x){if(x <1)a = 3;` (3认同)
  • @DavidBrown,为了确定函数是否将返回给定值,您必须确定该函数是否可以结束.在给出的示例中,有必要知道是否存在bar()将返回0的场景.这需要解决暂停问题. (2认同)
  • "...你不能编写一个程序来确定任何给定的程序是否会终止" - 实际上没有_executing_程序. (2认同)
  • 很好的答案,@ Caleb.但是,值得注意的是,虽然编写一个可以确定函数是否为_all_可能函数更改变量的编译器是不可能的,但是可以编写一个可以确定_certain_函数将修改变量的编译器. ,或者他们永远不会修改变量. (2认同)
  • 停止问题之间似乎存在一些混淆 - 这意味着存在一些*不可复制的事物*和难以解决的问题(例如NP-Hard问题),这些问题在计算时是不切实际的.停止问题源于计算的基本属性(实际上是计算是子集的数学 - 参见http://en.wikipedia.org/wiki/G%C3%B6del%27s_incompleteness_theorem) - 这是一个函数程序*和*输入 - 即使知道你无法确定程序是否会停止. (2认同)

orl*_*rlp 124

想象一下这样的编译器存在.我们还假设为方便起见,它提供了一个库函数,如果传递的函数修改给定变量则返回1,而当函数不修改时返回0.那么该程序应该打印什么?

int variable = 0;

void f() {
    if (modifies_variable(f, variable)) {
        /* do nothing */
    } else {
        /* modify variable */
        variable = 1;
    }
}

int main(int argc, char **argv) {
    if (modifies_variable(f, variable)) {
        printf("Modifies variable\n");
    } else {
        printf("Does not modify variable\n");
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 它实际上只是对[停止问题]不可判定性的着名证据的一个很好的改编(http://en.wikipedia.org/wiki/Halting_problem#Sketch_of_proof). (28认同)
  • 太好了!由程序员编写的[我是骗子悖论](http://en.wikipedia.org/wiki/Liar_paradox). (12认同)
  • 在这个具体的情况下,"modifies_variable"应该返回true:至少有一个执行路径,其中变量确实被修改.并且在调用外部非确定性函数之后达到该执行路径 - 因此整个函数是非确定性的.出于这两个原因,编译器应该采用pesimistic视图并确定它确实修改了变量.如果在确定性比较(可由编译器验证)之后达到修改变量的路径,则产生false(即"1 == 1"),那么编译器可以安全地说这样的函数永远不会修改变量 (10认同)
  • @JoePineda:问题是`f`是否修改了变量 - 而不是它是否可以修改变量.这个答案是对的. (6认同)
  • @JoePineda:没有什么能阻止我从编译器源复制/粘贴`modifies_variable`的代码,完全使你的参数无效.(假设是开源的,但重点应该是明确的) (4认同)
  • @KonstantinWeitz确切地说.此问题至少与暂停问题一样困难,因为编译器必须能够在修改变量之前判断程序是否停止以查看变量是否实际被修改. (2认同)
  • 首先这不是问题,应该是modifies_variable(f,variable).其次让我们假设modifies_variable()在从外部查看函数时是完全合理的.即使在那种(不可能的)情况下,这也会导致编译器无限循环.我认为这会混淆而不是通过引入包括测试的递归来突出问题.一个更简单的例子就是一个函数,它通过rand()或其他一些外部非静态可分析因子确定其执行路径,这是原始书籍试图传达和失败的原因. (2认同)

Blu*_*eft 60

不要混淆"将给出或将不会修​​改给定这些输入的变量",因为"具有修改变量的执行路径".

前者称为不透明谓词确定,并且很容易决定 - 除了停止问题的减少之外,您可以指出输入可能来自未知来源(例如用户).所有语言都是如此,而不仅仅是C++.

但是,后一种语句可以通过查看解析树来确定,这是所有优化编译器所做的事情.他们这样做的原因是纯函数 (以及引用透明函数,对于某些引用透明的定义)具有可以应用的各种好的优化,例如易于使用或在编译时确定其值; 但要知道函数是否纯粹,我们需要知道它是否可以修改变量.

所以,关于C++的一个令人惊讶的陈述实际上是一个关于所有语言的简单陈述.

  • 这是最好的答案imho,重要的是要做出这种区分. (5认同)
  • @Kip"通常无法决定"可能意味着"无法决定,证明是微不足道的". (2认同)

das*_*ght 28

我认为"C++函数是否会改变特定变量的值"中的关键词是"将".当然可以构建一个编译器来检查是否允许 C++函数更改特定变量的值,您无法肯定地说该更改将会发生:

void maybe(int& val) {
    cout << "Should I change value? [Y/N] >";
    string reply;
    cin >> reply;
    if (reply == "Y") {
        val = 42;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @MartinEpsz"can"我的意思是"允许改变",而不是"可能改变".我相信这是OP在谈论`const`-ness检查时的想法. (12认同)

Lar*_*rsH 15

我不认为有必要调用暂停问题来解释您在编译时无法通过算法知道给定函数是否会修改某个变量.

相反,它足以指出函数的行为通常取决于运行时条件,编译器事先无法知道.例如

int y;

int main(int argc, char *argv[]) {
   if (argc > 2) y++;
}
Run Code Online (Sandbox Code Playgroud)

编译器如何能够确定地预测是否y会被修改?


kri*_*iss 7

它可以完成,编译器一直在为某些函数执行它,例如,对于简单的内联访问器或许多纯函数,这是一个简单的优化.

在一般情况下,不可能知道它.

每当有来自另一个模块的系统调用或函数调用,或者对潜在的覆盖方法的调用时,任何事情都可能发生,包括一些黑客使用堆栈溢出来改变不相关的变量的恶意接管.

但是,您应该使用const,避免全局变量,更喜欢对指针的引用,避免重复使用不相关任务的变量等,这将使编译器在执行积极优化时更加轻松.


Tim*_*lds 6

解释这个有多种途径,其中一个是停机问题:

在可计算性理论中,暂停问题可以表述如下:"给定任意计算机程序的描述,确定程序是否完成运行或继续运行".这相当于在给定程序和输入的情况下决定程序在使用该输入运行时最终会停止还是将永远运行的问题.

Alan Turing在1936年证明,不存在解决所有可能的程序输入对的暂停问题的通用算法.

如果我写一个看起来像这样的程序:

do tons of complex stuff
if (condition on result of complex stuff)
{
    change value of x
}
else
{
    do not change value of x
}
Run Code Online (Sandbox Code Playgroud)

x变化的价值是什么?要确定这一点,首先必须确定do tons of complex stuff零件是否导致条件触发 - 或者甚至更基本,是否停止.这是编译器无法做到的.


Joh*_*tte 6

真的很惊讶,没有直接使用暂停问题的答案!从这个问题到停止问题的解决方案非常简单.

想象一下,编译器可以判断函数是否改变了变量的值.那么它肯定能够判断以下函数是否改变了y的值,假设在整个程序的其余部分中所有调用都可以跟踪x的值:

foo(int x){
   if(x)
       y=1;
}
Run Code Online (Sandbox Code Playgroud)

现在,对于我们喜欢的任何程序,让我们将其重写为:

int y;
main(){
    int x;
    ...
    run the program normally
    ...
    foo(x);
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果且仅当我们的程序更改了y的值时,它是否会终止 - foo()是它在退出之前做的最后一件事.这意味着我们已经解决了暂停问题!

上述减少告诉我们的是,确定变量值是否发生变化的问题至少与暂停问题一样困难.已知暂停问题是无法计算的,所以这个问题也必须如此.