By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
435,009 Members | 2,821 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 435,009 IT Pros & Developers. It's quick & easy.

Function call and type promotion

P: n/a
Hi everyone -

I am not quite sure to understand what is really going on when a
function defined in one translation unit calls a function defined in a
different translation unit without knowing its prototype. Let's say for
instance :

foo.c

#include <stdio.h>

void foo(float a, int b)
{
printf("%f (0x%x) %d (0x%x)\n", a, &a, b, &b);
}

main.c

int main(void)
{
float a = 5.0;
int b = 10;

foo(a, b);

return 0;
}

According to what I have read, the argument a should be promoted to
double, but the function foo expects to get a float as its first
parameter. The result is undefined as the binary representation / space
occupied by a float and a double are completely different. I'm OK with
that.

However, if I change the foo function definition to foo(char a, char b)
the arguments should be promoted to int. As foo would expect two char
as parameters the address of a and b should be *(ebp + 4) and *(ebp +
5). This is not the case, it appears that b is located sizeof(int)
deeper in the stack than a so the function displays the correct values.

I do not understand why the behavior is different ... did I miss
something related to type promotion and the circumstances under which
it occurs?
Thank you,
Yannick

Jun 27 '08 #1
Share this Question
Share on Google+
9 Replies


P: n/a
Yannick wrote:
Hi everyone -

I am not quite sure to understand what is really going on when a
function defined in one translation unit calls a function defined in a
different translation unit without knowing its prototype. Let's say for
instance :

foo.c

#include <stdio.h>

void foo(float a, int b)
{
printf("%f (0x%x) %d (0x%x)\n", a, &a, b, &b);
}

main.c

int main(void)
{
float a = 5.0;
int b = 10;

foo(a, b);

return 0;
}

According to what I have read, the argument a should be promoted to
double, but the function foo expects to get a float as its first
parameter. The result is undefined as the binary representation / space
occupied by a float and a double are completely different. I'm OK with
that.

However, if I change the foo function definition to foo(char a, char b)
the arguments should be promoted to int. As foo would expect two char as
parameters the address of a and b should be *(ebp + 4) and *(ebp + 5).
This is not the case, it appears that b is located sizeof(int) deeper in
the stack than a so the function displays the correct values.

I do not understand why the behavior is different ... did I miss
something related to type promotion and the circumstances under which it
occurs?
The only thing you've missed is that "undefined behavior"
does not guarantee some kind of run-time error, and might even
match what somebody was hoping for.

No function with a "promotable" parameter[*] can be called
correctly without having a prototype in scope at the point of
the call, for exactly the reason you've described: If there's
no prototype, the caller promotes the arguments and this means
they do not match the actual types the function expects. What
happens next is undefined: The program might abort, it might
run but give weird results, it might make vanilla pudding ooze
from your keyboard -- or it might "work." If it works, though,
it's because of luck (good? bad?) and not because of skill.
[*] Note that "K&R-style" functions never have promotable
parameters, even if they seem to be written that way:

void foo(f)
float f;
{ ... }

takes a non-promotable `double' parameter which is then demoted
to `float'. The function is roughly equivalent to

void foo(double _fake_f) {
float f = _fake_f;
...
}

--
Er*********@sun.com
Jun 27 '08 #2

P: n/a
Yannick <yd******@hotmail.comwrites:
I am not quite sure to understand what is really going on when a
function defined in one translation unit calls a function defined in a
different translation unit without knowing its prototype. Let's say
for instance :

foo.c

#include <stdio.h>

void foo(float a, int b)
{
printf("%f (0x%x) %d (0x%x)\n", a, &a, b, &b);
}

main.c

int main(void)
{
float a = 5.0;
int b = 10;

foo(a, b);

return 0;
}

According to what I have read, the argument a should be promoted to
double, but the function foo expects to get a float as its first
parameter. The result is undefined as the binary representation /
space occupied by a float and a double are completely different. I'm
OK with that.
Right, except that the binary representations *may or may not* be
completely different. (I've seen systems where a double is simply a
float with extra mantissa bits added at the end; on such a system,
with the right parameter passing conventions, a certain byte ordering,
and a strong tail-wind, it just might "work". Such is the nature of
undefined behavior.)
However, if I change the foo function definition to foo(char a, char
b) the arguments should be promoted to int.
Not quite. Remember, when the compiler is handing the call to foo, it
can't see the definition of foo, so nothing in the definition of foo
can affect how the arguments are promoted.

(First, an important piece of terminology: A *parameter* is an object,
local to a function, defined and declared between the parentheses in
the prototype. An *argument* is an expression passed to a function,
appearing between the parentheses in a function call. In the
evaluation of a function call, each argument is evaluated, and the
resulting value is assigned to the corresponding parameter.)

If there's no prototype determining the parameter type (i.e., either
there's no prototype at all or the argument corresponds to the "..."
in a variadic function), float is promoted to double. If foo happens
to be expecting a double argument, you're ok; if not, you're in the
land of undefined behavior.

If you passed a char *argument*, it would be promoted to int (or
possibly to unsigned int given sufficiently exotic system
characteristics). And you'd be ok if the function expected an int; if
it doesn't, you have UB.
As foo would expect two
char as parameters the address of a and b should be *(ebp + 4) and
*(ebp + 5). This is not the case, it appears that b is located
sizeof(int) deeper in the stack than a so the function displays the
correct values.
The *parameters* are of types float and int, respectively, because you
defined them that way. If you examine their addresses within the body
of foo, you're looking at where foo *expects* them to be. But since
you've already invoked undefined behavior, you'll *probably* get the
right addresses, but there are no guarantees.
I do not understand why the behavior is different ... did I miss
something related to type promotion and the circumstances under which
it occurs?
The point is that the behavior *isn't* different. The addresses of
the parameters shouldn't be affected by what was passed as arguments
(though their values certainly will be).
--
Keith Thompson (The_Other_Keith) ks***@mib.org <http://www.ghoti.net/~kst>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Jun 27 '08 #3

P: n/a
Understood. Thanks for the help!
Yannick

Jun 27 '08 #4

P: n/a
Yannick wrote:
>
I am not quite sure to understand what is really going on when a
function defined in one translation unit calls a function defined
in a different translation unit without knowing its prototype.
Let's say for instance :

foo.c

#include <stdio.h>

void foo(float a, int b) {
printf("%f (0x%x) %d (0x%x)\n", a, &a, b, &b);
}
You failed to #include "foo.h", which should read:

void foo(fload a, int b);
>
main.c

int main(void) {
float a = 5.0;
int b = 10;

foo(a, b);
return 0;
}
And here you also failed to #include "foo.h". The purpose of
header files is to make characteristics of cfiles available to
other cfiles, so that they can be correctly used.

.... snip musing on undefined behavior ...

--
[mail]: Chuck F (cbfalconer at maineline dot net)
[page]: <http://cbfalconer.home.att.net>
Try the download section.
** Posted from http://www.teranews.com **
Jun 27 '08 #5

P: n/a
On 2008-05-21 21:51:32 +0200, CBFalconer <cb********@yahoo.comsaid:
And here you also failed to #include "foo.h". The purpose of
header files is to make characteristics of cfiles available to
other cfiles, so that they can be correctly used.
[..] what is really going on when a function defined in one translation
unit calls a function defined in a different translation unit without
knowing its prototype. [...]

That's why I intently didn't use a header file for this one. I didn't
want the compiler to know about the function prototype.

I know one should always provide the compiler with functions prototypes
but that's not the point of this discussion.
Yannick

Jun 27 '08 #6

P: n/a
In article <2008052119135016807-ydaffaud@hotmailcom>
Yannick <yd******@hotmail.comwrote:
>I am not quite sure to understand what is really going on when a
function defined in one translation unit calls a function defined in a
different translation unit without knowing its prototype.
Depending on what the correct prototype is, this *can* even be
guaranteed to work. It may well work despite a lack of (Standard)
guarantee, however (as others have described else-thread).
>Let's say for instance :
[snippage; vertical space editing]
>void foo(float a, int b) { printf("%f (0x%x) %d (0x%x)\n", a, &a, b, &b); }
...
>However, if I change the foo function definition to foo(char a, char b) ...
the address of a and b should be *(ebp + 4) and *(ebp + 5).
Given that you mention "ebp", we can guess (without any absolute
guarantee of being right, especially someday in the future when
some other manufacturer uses that name for something entirely
different) that you refer to the x86 architecture, and one of
the common C compilers for that architecture.
>This is not the case, it appears that b is located sizeof(int)
deeper in the stack than a so the function displays the correct values.
Most x86 compilers, most of the time, under mostly-default conditions
-- and all these "most"s are in fact important -- "just happen"
(for a number of historical reasons) to pass "float" parameters as
"double"s on the hardware-provided stack addressed via %esp (I use
this wording to distinguish it from the FPU stack). They pass
"char" parameters only after widening them to "int", again on the
hardware-provided stack addressed via %esp.

Most of these compilers (under said conditions) do this whether or
not there is a prototype present at the site of the call, and hence
produce runtime code for foo() that assumes that the "float"
parameter was widened to "double", and that the "char" parameters
were widened to "int".

C compilers do not have to do this, and if they choose not to be
compatible with "most" x86 C compilers, they *can* use some sort
of "better" parameter-passing mechanism(s). Many C compilers can
even be told to use these "better" mechanisms even while maintaining
some compatibility, either on a function-by-function basis (using
#pragma or __attribute__ or __fastcall or __other_magic_word, or
perhaps on a more global basis, by something like -mregparm=N).
The "better" conventions *can* be the *default*: since Standard C
says that the C programmer, not the implementation, is in the wrong
for failing to provide proper prototypes, Standard C allows the
code to fail in the absence of proper prototypes. It is merely
the (strong) draw of compatibility (and/or convenience) that keeps
C compiler vendors using the "worse" calling conventions that make
poorly-written, non-Standard code "work" anyway.

("Better" is in quotes since "better"-ness is necessarily somewhat
an "eye of the beholder" thing. The "best" way to do something is
a lot like the "best" flavor of ice cream.)
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (4039.22'N, 11150.29'W) +1 801 277 2603
email: gmail (figure it out) http://web.torek.net/torek/index.html
Jun 27 '08 #7

P: n/a
Yannick wrote:
CBFalconer <cb********@yahoo.comsaid:
>And here you also failed to #include "foo.h". The purpose of
header files is to make characteristics of cfiles available to
other cfiles, so that they can be correctly used.

[..] what is really going on when a function defined in one
translation unit calls a function defined in a different
translation unit without knowing its prototype. [...]

That's why I intently didn't use a header file for this one. I
didn't want the compiler to know about the function prototype.

I know one should always provide the compiler with functions
prototypes but that's not the point of this discussion.
Undefined behavious is, well, undefined. There is no discussion.

--
[mail]: Chuck F (cbfalconer at maineline dot net)
[page]: <http://cbfalconer.home.att.net>
Try the download section.
** Posted from http://www.teranews.com **
Jun 27 '08 #8

P: n/a
CBFalconer <cb********@yahoo.comwrites:
Yannick wrote:
>CBFalconer <cb********@yahoo.comsaid:
>>And here you also failed to #include "foo.h". The purpose of
header files is to make characteristics of cfiles available to
other cfiles, so that they can be correctly used.

[..] what is really going on when a function defined in one
translation unit calls a function defined in a different
translation unit without knowing its prototype. [...]

That's why I intently didn't use a header file for this one. I
didn't want the compiler to know about the function prototype.

I know one should always provide the compiler with functions
prototypes but that's not the point of this discussion.

Undefined behavious is, well, undefined. There is no discussion.
There was a discussion, and it was fairly interesting. Perhaps you
missed it. It included some good points about why the behavior is
undefined, as well as circumstances when it isn't undefined (when the
promoted argument types match the declared parameter types). And,
yes, it also included some harmless digressions into the actual
behavior a system is likely to exhibit in the presence of this
undefined behavior; this was relevant to the reasons that the standard
defines (or undefines) things the way it does, and to the practice of
diagnosing problems caused by undefined behavior.

--
Keith Thompson (The_Other_Keith) ks***@mib.org <http://www.ghoti.net/~kst>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Jun 27 '08 #9

P: n/a
Keith Thompson <ks...@mib.orgwrote:
CBFalconer <cbfalco...@yahoo.comwrites:
Yannick wrote:
I know one should always provide the compiler
with functions prototypes but that's not the
point of this discussion.
Undefined behavious is, well, undefined. *There
is no discussion.

There was a discussion, and it was fairly interesting.
Perhaps you missed it. *It included some good points
about why the behavior is undefined, as well as
circumstances when it isn't undefined (when the
promoted argument types match the declared parameter
types). *And, yes, it also included some harmless
digressions into the actual behavior a system is
likely to exhibit in the presence of this
undefined behavior;
IMHO, the last bit is not harmless.
this was relevant to the reasons that the standard
defines (or undefines) things the way it does, and
to the practice of diagnosing problems caused by
undefined behavior.
Unfortunately, many newbies will focus exclusively
on the behaviour of undefined behaviour and in some
cases end up convincing themselves that there is no
other possible behaviour from any other system.
Worst case scenario, they will use the construct
and conclude that any system that doesn't exhibit
the desired behaviour is flawed and not worth
considering as a target platform.

In this case, the problem of unprotoyped functions
is so old and so well known, very few compilers
will not provide a facility for at least warning
of unprototyped function use.

Prevention is better than cure. C99 requires at
least a declaration, but I prefer to break
conformance and, were possible, force the compiler
to error out if I use an unprototyped function.

--
Peter
Jun 27 '08 #10

This discussion thread is closed

Replies have been disabled for this discussion.