views:

135

answers:

6

For a class, I'm writing a simple crypt function. Everything works as expected:

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;
}

Using the professor's test case, everything works. But when I initially wrote the callbacks, I ommited the return. They were coded up like:

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
}

When I ran the code with the non-returning callbacks, the output was still correct when I applied the test case (and yes, I recompiled, I wasn't running the old code). I only noticed the 'error' when I compiled with -Wall.

So I'm confused. Why is c (in crypt()) getting the correct value after it is assigned to the return value of callback (when callback doesn't return anything)? c isn't a pointer, it's just a regular int.

P.S. The assignment has nothing to do with function pointers.

+7  A: 

Failing to use return in a function that has a return type other than void is undefined behaviour, if the caller tries to use the return value of the function.

In this case, the undefined behaviour you were getting apparently was "it returned the value I wanted it to, anyway". You just got (un)lucky. (It has nothing to do with function pointers, though).


If you are compiling on x86, then the underlying reason it worked out is that most x86 calling conventions specify that a return value of type int is returned in the %eax register. In your case, the compiler also decided to use this register to calculate the new value of c - so the value just happened to be left there to reappear as the "return value". If you'd done some more calculations in the function after calculating c, you'd have seen something else.

caf
Right. Very good answer. UB includes "just what I wanted" as one of an infinite amount of possibilities.
sbi
+1  A: 

This might probably have to do with the way, function values are returned on your platform. For example, on many x86 based platforms, the register EAX is used to return integer values. If the correct value happens to be in that register at the end of the function (due to the computation being made), then the caller will not perceive a difference: it's just as if the value had been put there intentionally with a return statement.

It's just (bad!) luck, that your code actually worked the first time around.

Dirk
+1  A: 

What comes to my mind(although it could be silly) is that c is assigned to a register that is used as the "return" register in your architecture.

AraK
That would be my guess too.
Jay
not silly, quite likely this is the answer.
dwelch
A: 

It's not defined what it will do, since that's supposed to be an unrecoverable compile error. I would look at the makefile/ide/compiler and see why it continued to produce an executable when it shouldn't have

Jay
A: 

The Intel x86 architecture is BASICALLY that of an accumulator machine. This means that there is one register that is strongly preferred for arithmetic and logical results. Usually, compilers for such a machine architecture will emit code that leaves results in that register, and stores from that register. Function return conventions implemented by those compilers will usually specify the return value to be in that register, because that is the cheapest place for it to be (since that's where the code that calculated it probably left it). All this adds up to "You got lucky."

John R. Strohm
A: 

disassemble your program and you will probably see what happened. Taking one of your functions the only difference to c = something and c = something; return(c); is the addition of the line above the popq %rbx. My x86 is rusty but I think that answers it. eax happens to end up with the result and either eax or -12 in the stack is where the return value is kept for an x86 return.

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
dwelch
you did get lucky, BTW. I started this answer by compiling for ARM and you would not have been lucky with ARM because there are more useful registers in an ARM and r0 was not the last register used for your particular code. When you add optimization, at least on the arm, gcc realizes your code does nothing as it returns nothing and touches no globals so the optimizer removed the function entirely.
dwelch