473,386 Members | 1,674 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,386 software developers and data experts.

Function call and type promotion

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
9 2343
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
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
Understood. Thanks for the help!
Yannick

Jun 27 '08 #4
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
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
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 (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: gmail (figure it out) http://web.torek.net/torek/index.html
Jun 27 '08 #7
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
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
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 thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

31
by: nikola | last post by:
Hi all, I was working with a simple function template to find the min of two values. But since I would like the two values to be different (type) I dont know what kind of value (type) it will...
8
by: BigMan | last post by:
Can someone cite the rules for type promotion in C++? And, in particular, what is the type of the result of adding 2 values of type char?
6
by: bluekite2000 | last post by:
I have Vector<complex<float> > V(5); V.rand(); Vector<float> V1(V); //specialized function here to return norm(V). This works fine Vector<double> V2(5); V2.rand(); Vector<float>...
28
by: Michael B. | last post by:
I tend to use rather descriptive names for parameters, so the old style of declaration appeals to me, as I can keep a declaration within 80 chars: void * newKlElem...
18
by: Razvan | last post by:
Hi! What is the purpose of such a function ? int function(void)
3
by: Beta What | last post by:
Hello, I have a question about casting a function pointer. Say I want to make a generic module (say some ADT implementation) that requires a function pointer from the 'actual/other modules'...
5
by: kris | last post by:
Hi I have written a program which prints the hostid on a linux system. The programm uses gethostid() function call which is defined in the library file unistd.h. But my programm gets compiled...
3
by: =?ISO-8859-15?Q?Jean=2DFran=E7ois?= Lemaire | last post by:
Hello, I'm having a discussion with someone who sustains that function parameters, when they are smaller than an int (say short or char) are automatically promoted to int before being passed to...
8
by: vaib | last post by:
hi all , It really seems that C never ceases to amaze . All this time i've been doing C and i thought i was quite adept at it but i was wrong . So without wasting any more time , here's the...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.