1

Trying to clearly state the semantics of a function call.

In calling a function, are the arguments passed to the function the ones the calling code initially gives or the ones the function receives?


With code like below, the calling code in bar() calls foo() twice. This first time with int 2, then ...

1) Function foo() is called with a double 3.1, x has the converted value of int 3,

or

2) The value double 3.1 is converted to int 3 and function foo() is called,

or

3) Something else.

IOWs, is the conversion of double 3.1 to int 3 part of the call of a function (the conversion would not happen without the function call) or is it a preceding activity (consider to be part of the calling code) before the function call?

void foo(int x);

void bar() {
  foo(2);
  foo(3.1);
}

This query is primarily C, yet language agnostic thoughts appreciated. A quick answer is not needed.

[Edit]

Note: This is not an question of how platforms create the binary/executable to implement the program code - just about the code/language.

[Edit 2] @ Erik Eidt useful comment provided better words to use (at least for C) for this question.

Perhaps a more succinct question would be, from the language perspective: "Are functions called with "actual arguments" or "formal parameters"?

C11 3.3 actual arguments and 3.16 1 formal parameter.

4
  • Maybe I've gotten confused, but it looks like you got foo and bar mixed up a few times in your explanation so I'm not quite sure what you're asking. That said, I suspect the answer is "the compiler can put the conversion wherever it wants" because it would never have a noticeable effect on your program's behavior either way. In fact, for your exact sample I'd expect a compiler to drop the runtime conversion entirely and just output the code for a foo(3) call.
    – Ixrec
    Aug 28, 2015 at 19:22
  • @Ixrec Thanks for the foo/bar correction. Yes a compiler can optimize to foo(3). This example is a simplification as the code could be double x = fred(); foo(x). I am more questioning the language concepts and not so much what binary a compiler might render. Aug 28, 2015 at 19:32
  • 1
    FYI, The ones the calling code initially gives are called actual arguments, actual parameters, or actuals, where as the ones the function receives are called formal arguments, formal parameters, or formals.
    – Erik Eidt
    Aug 28, 2015 at 20:42
  • @Erik Eidt Than that is very useful. C11 3.3 actual arguments and 3.16 1 formal parameter. Aug 28, 2015 at 20:56

4 Answers 4

1

I expect in every implementation of C, the conversion happens first. The psuedo-assembly for bar:

store 2 in argument-register-0
call foo
store 3.1 in temporary-register-0
convert-to-int temporary-register-0 into temporary-register-1
store temporary-register-1 in argument-register-0
call foo

Your optimizer is probably smart enough to eliminate temporary-register0 and/or temporary-register-1, and different assembly versions may need to do extra trickery to deal with floating point - but the core steps are generally the same.

4
  • Yes, certainly the conversion happens first. My query is should the conversion be considered part of the calling code or part of the set-up in the function call? Much like 2 function calling styles delegates stack clean-up differently (at the function end or calling code), that is a compiler implementation detail and not a language one. Trying to determine if the conversion, from a language point-of-view, is "part of the function" or "part of the calling/parent code". Aug 28, 2015 at 19:42
  • It is a part of calling code. Certainly
    – porton
    Aug 28, 2015 at 19:53
  • @chux - the psuedocode is for the calling/parent code. I believe C does not specify how the code is compiled (you could automatically overload bar to do the conversion for example), but I would be surprised if any implementation did that.
    – Telastyn
    Aug 28, 2015 at 22:06
  • 1
    FWIW, you can see in gcc that the code to do the conversion is part of the calling code. The compiler also will convert the literal 3.1 to 3.
    – user40980
    Aug 28, 2015 at 22:51
1

Conversion is always done by the calling code, not in the called function (in particular because types get erased at runtime: the implementation does not know about types at runtime, only at compile-time). A C function which is declared (at least those which are not variadic, e.g. without ... ending there argument type list, like printf) has a well defined signature, given by its prototype.

Practically, every called function should have a prototype these days, often provided in some header file.

This is covered by §6.5.2.2 Function Calls of the standard (citing the n1570 draft of C11 standard, paragraph 7):

If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type.

So your case 2 apply.

Practically speaking, on many implementations of the C standard, the ABI document specifies the calling conventions. Read as an example the SVR4 ABI for x86-64. Often, arguments (at least the first few ones) are passed in registers, and different registers are used for int formals and for double ones. Since you gave a prototype for foo its int argument is required to be passed by the %rdi register (and %xmm0 would be used to pass a float formal argument) so the calling code in the caller function would load that integer register (perhaps by converting a float value).

BTW, in your example foo(3.1) the conversion to 3 is likely to be optimized by the compiler at compile time (constant folding) so the compiler would very probably emit the same assembler code as for foo(3) since you declared with a prototype void foo(int x); before the call.

If using GCC you should compile some simple sample code in file foo.c with gcc -fverbose-asm -Wall -O -S foo.c and look into the generated assembler file foo.s

There are some languages (like Common Lisp, Scheme, Perl, Python, Ruby) with dynamic typing. Then, each value knows its type (it is often represented in machine as some aggregate or structure with some of the fields giving the type) and your question does not really make any sense (you could understand that these language implementations handle only one value type, which would be a tagged union).

0

In C++ you have function overloading, that is given a series of functions with the same name but different input parameters (the return value is not considered) the compiler must look for the better pick, taking into account implicit conversions and namespace resolution.

So functionally the compiler finds a function call with a given input, and then looks if some overload fits the bill.

There could be well be more than one possible eligible overload, so it tries to find the best one which is then called.

In this sense the conversion is done before calling, since at first you don't even know which function to call.

0

Consider a language L0 that does no implicit conversions like double to int, "default argument promotions", array conversions to "address of first object", etc. Code like foo(int); double x; foo(x); is then a compile time error.

double x;
foo(x); // Bad L0 code

L0 is not relevant to this question other than it shows if L0 code wanted to call foo(int) with a double, it would explicitly perform the conversion.

double x;
foo(double_to_int(x));  // L0: Explicit conversion before calling `foo()`.

Consider a language L1 that does all the above implicit conversions. When code has a call to function foo(int) and a double parameter is passed, a function is created that that accepts the double, converts it to an int and then performs the code of foo(int). The L1 compiler has at least 4 choices: make 2 independent functions, 2 functions where one calls the other, makes one function with multiple entry points: one for int arguments and another for double, or one function with type information added in the passed argument.

double x;
foo(x); // Good L1 code, no conversion done in the calling code.

C is certainly not LO with C's implicit conversions. C could be L1 as there is little in the C spec that defines how to organize its passing of arguments - its ABI. As @Erik Eidt provided with useful comments to some key definitions, a language like C (and L1), which has a potential difference between actual arguments and formal parameters.

This muddies the boundary between calling code and the called function.


In the end, with C, it is the formal parameter definition which says "acquires a value on entry to the function" implying the function is not called yet until this point.


Ref

C11 3.3
actual argument
expression in the comma-separated list bounded by the parentheses in a function call expression, or a sequence of preprocessing tokens in the comma-separated list bounded by the parentheses in a function-like macro invocation

C11 3.16
formal parameter
object declared as part of a function declaration or definition that acquires a value on entry to the function, or an identifier from the comma-separated list bounded by the parentheses immediately following the macro name in a function-like macro definition

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.