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);
}
据说(外语源代码),尾部递归有两个特点:
&&
是否影响尾部递归
最重要的是,是上面的代码尾递归吗?如前所述,它有点不确定。原因是,从技术上讲,函数应该重新获得控制权,以便
&&
知道返回什么结果。(不过,这很容易优化,大多数编译器可能会这样做。)
为了确保它是尾部递归的,只需避免对结果执行任何操作,而不是返回结果
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所说的,只需避免对结果执行任何操作,而不是返回它。