C 即使存在逻辑and,这种尾部递归是否仍然存在?

C 即使存在逻辑and,这种尾部递归是否仍然存在?,c,recursion,tail-recursion,C,Recursion,Tail Recursion,我知道这个标题是重复的,但这是不同的,我无法得到一个线索,从前面的问题。所以我不得不再问一次 守则: int sum_n(int n, int *sum) { return n && (*sum += n) && sum_n(n - 1, sum); } 据说(外语源代码),尾部递归有两个特点: 该函数是自调用的 只需要恒定的堆栈空间 那么,这两个特性是判断尾部递归的唯一关键因素吗?return语句中的逻辑运算符&&是否影响尾部递归 最重要的是,是上面的

我知道这个标题是重复的,但这是不同的,我无法得到一个线索,从前面的问题。所以我不得不再问一次

守则:

int sum_n(int n, int *sum)
{
    return n && (*sum += n) && sum_n(n - 1, sum);
}
据说(外语源代码),尾部递归有两个特点:

  • 该函数是自调用的
  • 只需要恒定的堆栈空间
  • 那么,这两个特性是判断尾部递归的唯一关键因素吗?return语句中的逻辑运算符
    &&
    是否影响尾部递归


    最重要的是,是上面的代码尾递归吗?

    如前所述,它有点不确定。原因是,从技术上讲,函数应该重新获得控制权,以便
    &&
    知道返回什么结果。(不过,这很容易优化,大多数编译器可能会这样做。)

    为了确保它是尾部递归的,只需避免对结果执行任何操作,而不是返回结果

    int sum_n(int n, int *sum)
    {
        if (!(n && (*sum += n))) return 0;
        return sum_n(n - 1, sum);
    }
    

    我认为,这个函数不是尾部递归

            .file   "tail.c"
            .text
            .globl  sum
            .align  16, 0x90
            .type   sum,@function
    sum:                                    # @sum
            .cfi_startproc
    # BB#0:
            testl   %edi, %edi
            je      .LBB0_6
    # BB#1:                                 # %.lr.ph
            movl    (%rsi), %eax
            .align  16, 0x90
    .LBB0_3:                                # =>This Inner Loop Header: Depth=1
            addl    %edi, %eax
            je      .LBB0_4
    # BB#2:                                 # %tailrecurse
                                            #   in Loop: Header=BB0_3 Depth=1
            decl    %edi
            jne     .LBB0_3
    # BB#5:                                 # %tailrecurse._crit_edge
            movl    %eax, (%rsi)
    .LBB0_6:
            xorl    %eax, %eax
            ret
    .LBB0_4:                                # %split
            movl    $0, (%rsi)
            xorl    %eax, %eax
            ret
    .Ltmp0:
            .size   sum, .Ltmp0-sum
            .cfi_endproc
    
            .section        ".note.GNU-stack","",@progbits
    
    首先,我同意以下形式是尾部递归:

    int sum_n(int n, int *sum)
    {
        int tmp = n && (*sum += n);
        if (!tmp)
            return 0;
        else
            return sum_n(n - 1, sum);
    }
    
    然而,上面的函数并不完全等同于您原来的函数。您提供的代码应与以下代码等效:

    int sum_n(int n, int *sum)
    {
        int tmp = n && (*sum += n);
        if (!tmp)
            return 0;
        else
            return sum_n(n - 1, sum) ? 1 : 0;
    }
    

    不同的是,
    sum\u n(n-1,sum)
    的返回值不能直接用作函数的返回值:它应该从
    int
    转换为
    \u Bool

    而不是谈论尾部递归的学术定义,但是,clang 3.3将sum()编译为循环,而不是递归

            .file   "tail.c"
            .text
            .globl  sum
            .align  16, 0x90
            .type   sum,@function
    sum:                                    # @sum
            .cfi_startproc
    # BB#0:
            testl   %edi, %edi
            je      .LBB0_6
    # BB#1:                                 # %.lr.ph
            movl    (%rsi), %eax
            .align  16, 0x90
    .LBB0_3:                                # =>This Inner Loop Header: Depth=1
            addl    %edi, %eax
            je      .LBB0_4
    # BB#2:                                 # %tailrecurse
                                            #   in Loop: Header=BB0_3 Depth=1
            decl    %edi
            jne     .LBB0_3
    # BB#5:                                 # %tailrecurse._crit_edge
            movl    %eax, (%rsi)
    .LBB0_6:
            xorl    %eax, %eax
            ret
    .LBB0_4:                                # %split
            movl    $0, (%rsi)
            xorl    %eax, %eax
            ret
    .Ltmp0:
            .size   sum, .Ltmp0-sum
            .cfi_endproc
    
            .section        ".note.GNU-stack","",@progbits
    
    使用命令编译:

    $ clang -c tail.c -S -O2
    
    叮当声版本:

    $ clang -v
    clang version 3.3 (tags/RELEASE_33/final)
    Target: x86_64-unknown-linux-gnu
    Thread model: posix
    

    我认为是的,这是一个特例,因为存在
    &&
    操作符。因为,关于实现细节,
    &&
    操作的LHS值不需要为了计算最终结果而被保留,一个(假设的)编译器可以只发出一个“如果为零则跳转”指令,后跟尾部调用(即,它可以用
    sum\n(n,sum)
    的帧替换
    sum\n的帧)(n-1,sum)
    。相关:此函数永远不会返回1。Never@UmNyobe对,它永远不会返回1。这会影响什么吗?我不太同意你关于等价代码的看法。为了等价,我认为else子句应该是
    returntmp&&sum\n(n-1,sum)
    @UmNyobe当然它永远不会返回1。但是,这是您人类在分析函数和算法本身后得出的结论。根据语言规范,请认为您是一名编译器。谢谢。@UmNyobe在原始代码中,
    return
    语句的表达式是
    类型>_Bool
    ,我知道只有一种方法可以根据ISO/IEC 9899:1999将
    int
    转换为
    \u Bool
    。这是一个很好的观点。我认为尾部递归是独立于编译器的,可以通过语言标准本身来判断。然而,只有在使用特定编译器编译代码时,结论可以最终验证。由于编译器优化了return语句中的
    &&
    操作,我们得到了不同的结论,因此它确实依赖于编译器。我得到了一个要点,如果我必须确保它是尾部递归,就像@cHao所说的,只需避免对结果执行任何操作,而不是返回它。