-6

In a C program, there is a swap function and this function takes a parameter called x.I expect it to return it by changing the x value in the swap function inside the main function.

When I value the parameter as a variable, I want it, but when I set an integer value directly for the parameter, the program produces random outputs.

#include <stdio.h>

int swap (int x) {

    x = 20;
    
}

int main(void){

    int y = 100;
    
    int a = swap(y);   

    printf ("Value: %d", a);

    return 0;
}

Output of this code: 100 (As I wanted)

But this code:

#include <stdio.h>

int swap (int x) {

    x = 20;
    
}

int main(void){
    
    int a = swap(100);   

    printf ("Value: %d", a);

    return 0;
}

Return randomly values such as Value: 779964766 or Value:1727975774.

Actually, in two codes, I give an integer type value into the function, even the same values, but why are the outputs different?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
devilsgrin
  • 43
  • 5
  • `touch` does not return anything. Also, `x` is a local variable, changing it will have no effect on the caller. Also also where is the `swap` even? What does this have to do with assembly? – Jester Feb 26 '21 at 22:45
  • Enable compiler warnings, e.g. use `gcc -Wall` – ilkkachu Feb 26 '21 at 22:46
  • 2
    `return` statement missing from `touch()`, you can't just declare `int x = 20;` and expect the function to return `20` – Govind Parmar Feb 26 '21 at 22:47
  • @JesterI forgot to write swap, I edited it. If the swap function returns x, it will always return 20, I don't want that. I want to interfere with this from outside – devilsgrin Feb 26 '21 at 22:51
  • @GovindParmar No, I want to replace the value of x with the value I got from the parameter. So I don't want to define a local variable. – devilsgrin Feb 26 '21 at 22:54
  • C passes parameters by value and the parameter `x` is purely local to `swap`; changes to it don't affect any other program variables. Can you explain more precisely what you want the function `swap` to do, maybe with concrete examples? I'm afraid your current explanation doesn't make any sense to me. – Nate Eldredge Feb 26 '21 at 23:05
  • In general, if you don't include a `return` statement in a function, you can't expect its return value to be meaningful, and using it is undefined behavior. It's not necessarily a fatal compiler error, but you should definitely get a warning, and should not ignore it. The fact that it happens to be 100 for your first program is pure coincidence based on what registers the compiler happened to use for what, and may change unpredictably with different optimization options, etc. – Nate Eldredge Feb 26 '21 at 23:08

2 Answers2

1

You need to use return or use pointer.

  1. Using return function.
#include <stdio.h>

int swap () {

    return 20;
    
}

int main(void){
    
    int a = swap(100);   

    printf ("Value: %d", a);

    return 0;
}
  1. Using pointer function.
#include <stdio.h>

int swap (int* x) {

    (*x) = 20;
    
}

int main(void){
    
    int a;

    swap(&a);   

    printf ("Value: %d", a);

    return 0;
}
1

First of all, C functions are call-by-value: the int x arg in the function is a copy. Modifying it doesn't modify the caller's copy of whatever they passed, so your swap makes zero sense.

Second, you're using the return value of the function, but you don't have a return statement. In C (unlike C++), it's not undefined behaviour for execution to fall off the end of a non-void function (for historical reasons, before void existed, and function returns types defaulted to int). But it is still undefined behaviour for the caller to use a return value when the function didn't return one.

In this case, returning 100 was the effect of the undefined behaviour (of using the return value of a function where execution falls off the end without a return statement). This is a coincidence of how GCC compiles in debug mode (-O0):

GCC -O0 likes to evaluate non-constant expressions in the return-value register, e.g. EAX/RAX on x86-64. (This is actually true for GCC across architectures, not just x86-64). This actually gets abused on codegolf.SE answers; apparently some people would rather golf in gcc -O0 as a language than ANSI C. See this "C golfing tips" answer and the comments on it, and this SO Q&A about why i=j inside a function putting a value in RAX. Note that it only works when GCC has to load a value into registers, not just do a memory-destination increment like add dword ptr [rbp-4], 1 for x++ or whatever.


In your case (with your code compiled by GCC10.2 on the Godbolt compiler explorer)

int y=100; stores 100 directly to stack memory (the way GCC compiles your code).

int a = swap(y); loads y into EAX (for no apparent reason), then copies to EDI to pass as an arg to swap. Since GCC's asm for swap doesn't touch EAX, after the call, EAX=y, so effectively the function returns y.

But if you call it with swap(100), GCC doesn't end up putting 100 into EAX while setting up the args.

The way GCC compiles your swap, the asm doesn't touch EAX, so whatever main left there is treated as the return value.

main:
...
        mov     DWORD PTR [rbp-4], 100          # y=100

        mov     eax, DWORD PTR [rbp-4]          # load y into EAX
        mov     edi, eax                        # copy it to EDI (first arg-passing reg)
        call    swap                            # swap(y)

        mov     DWORD PTR [rbp-8], eax          # a = EAX as the retval = y
...

But with your other main:

main:
...                                    # nothing that touches EAX
        mov     edi, 100
        call    swap
        mov     DWORD PTR [rbp-4], eax   # a = whatever garbage was there on entry to main
...

(The later ... reloads a as an arg for printf, matching the ISO C semantics because GCC -O0 compiles each C statement to a separate block of asm; thus the later ones aren't affected by the earlier UB (unlike in the general case with optimization enabled), so do just print whatever's in a's memory location.)

The swap function compiles like this (again, GCC10.2 -O0):

swap:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-4], 20
        nop
        pop     rbp
        ret

Keep in mind none of this has anything to do with valid portable C. This (using garbage left in memory or registers) one of the kinds of things you see in practice from C that invokes undefined behaviour, but certainly not the only thing. See also What Every C Programmer Should Know About Undefined Behavior from the LLVM blog.

This answer is just answering the literal question of what exactly happened in asm. (I'm assuming un-optimized GCC because that easily explains the result, and x86-64 because that's a common ISA, especially when people forget to mention any ISA.)

Other compilers are different, and GCC will be different if you enable optimization.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Yes, that was what I really wanted to learn. People scored minus points for misunderstanding my question, although that's what I really wanted to know. Thank you so much ! – devilsgrin Feb 27 '21 at 13:50
  • @devilsgrin: I wondered, since you tagged asm. You could have made the question better by actually including the compiler-generated asm *in the question*, or at least saying what compiler for what architecture you were using. And asking why it happens to work, or something that indicates you know it's not valid C, and you want to know what coincidence in asm made it work. The way the question is written (except for the [assembly] tag), makes it sound like a question about how to use C properly. I'm not surprised it got a lot of downvotes, and an answer like Danylo's (which I upvoted). – Peter Cordes Feb 28 '21 at 01:17
  • 1
    Yes, I was labeling the assembly because I was wondering how this was on the assembly side but it was misunderstood because of the way I asked the question. I will take your suggestions into consideration, thank you very much again for your good reply and explanation :) @Peter Cordes – devilsgrin Feb 28 '21 at 23:08
  • @devilsgrin: You can still edit the question now to make it clearer for future readers, and to make it easier for them to find with searches if they're wondering the same thing. Also, so future editors tidying up the question won't be inclined to remove the [assembly] tag, like someone already did. You edited to put the tag back, but didn't do anything else to make the question clearer while you were editing. – Peter Cordes Mar 01 '21 at 01:14
  • Yes I know. Soon I will be editing the question in a better way. – devilsgrin Mar 02 '21 at 20:04