对于一个类,我正在编写一个简单的crypt函数。一切都按预期工作:
int crypt(char *key, int (*callback)(int, char*, int, int))
{
int c, i = 0, n = strlen(key);
while((c = fgetc(stdin)) != EOF)
{
// only change the char if it's printable
if(c >= 32 && c <= 126)
c = callback(c, key, n, i);
fputc(c, stdout);
i++;
}
}
int encryptCallback(int c, char *key, int n, int i)
{
return (c - 32 + key[i % n]) % 95 + 32;
}
int decryptCallback(int c, char *key, int n, int i)
{
return (c - 32 - key[i % n] + 3 * 95) % 95 + 32;
}
使用教授的测试用例,一切正常。但是当我最初编写回调时,我忽略了返回。它们的编码如下:
int encryptCallback(int c, char *key, int n, int i)
{
c = (c - 32 + key[i % n]) % 95 + 32; // no return
}
int decryptCallback(int c, char *key, int n, int i)
{
c = (c - 32 - key[i % n] + 3 * 95) % 95 + 32; // no return
}
当我使用非返回回调运行代码时,在我应用测试用例时输出仍然正确(是的,我重新编译,我不是'运行旧代码)。当我用-Wall编译时,我只注意到'错误'。
所以我很困惑。为什么c
(在crypt()
中)在分配给callback
的返回值(回调不返回任何内容)后得到正确的值? c
不是指针,它只是一个常规的int。
P.S。 The assignment与函数指针无关。
答案 0 :(得分:7)
如果调用者尝试使用函数的返回值,则在return
以外的返回类型的函数中未能使用void
是未定义的行为。
在这种情况下,您明显看到的未定义行为是“它返回了我想要的值,无论如何”。你刚刚(非)幸运。 (但它与函数指针无关。)
如果要在x86上进行编译,那么它解决的根本原因是大多数x86调用约定指定在int
寄存器中返回类型为%eax
的返回值。在您的情况下,编译器还决定使用此寄存器来计算c
的新值 - 因此恰好将该值重新显示为“返回值”。如果你在计算c
后在函数中做了一些更多的计算,你就会看到别的东西。
答案 1 :(得分:1)
这可能与方式有关,在您的平台上返回函数值。例如,在许多基于x86的平台上,寄存器EAX用于返回整数值。如果正确的值碰巧在函数末尾的那个寄存器中(由于计算正在进行),那么调用者就不会感觉到差异:就好像这个值是故意放在那里{{1声明。
幸运的是,你的代码实际上是第一次工作。
答案 2 :(得分:1)
我想到的是(尽管可能很愚蠢)是c
被分配给一个用作架构中“返回”寄存器的寄存器。
答案 3 :(得分:0)
它没有定义它将做什么,因为那应该是一个不可恢复的编译错误。我会看看makefile / ide /编译器,看看为什么它不应该生成可执行文件
答案 4 :(得分:0)
Intel x86架构 BASICALLY 累加器机器。这意味着有一个寄存器非常适合算术和逻辑结果。通常,这种机器架构的编译器会发出代码,将结果留在该寄存器中,并从该寄存器中存储。这些编译器实现的函数返回约定通常会指定该寄存器中的返回值,因为这是它最便宜的地方(因为那是计算它的代码可能离开它的地方)。所有这些加起来“你很幸运。”
答案 5 :(得分:0)
反汇编您的程序,您可能会看到发生了什么。将你的一个函数作为c = something和c = something的唯一区别;返回(C);是popq%rbx上方的行的添加。我的x86生锈了,但我认为它可以解决它。 eax恰好以结果结束,并且堆栈中的eax或-12是为x86返回保留返回值的位置。
imull $95, %eax, %eax
movl %ecx, %edx
subl %eax, %edx
movl %edx, %eax
addl $32, %eax
movl %eax, -12(%rbp)
movl -12(%rbp), %eax
popq %rbx
leave
ret
.cfi_endproc