0

I'm learning C. I have done some little experimentswhen I'm learning some chapters.

The major question is I can't understand why the result of the code's execution is following, because it doesn't meet what I thought code would go.

source code:

#include <stdio.h>

int imax();

int main(void)
{ 
    printf("%zd %zd\n", sizeof(int), sizeof(double));
    printf("The maximum of %d and %d is %d.\n", 3, 5, imax(3));
    printf("The maximum of %d and %d is %d.\n", 3, 5, imax(3.0, 1000.0));
    printf("The maximum of %d and %d is %d.\n", 3, 5, imax(3.0));
    printf("The maximum of %d and %d is %d.\n", 3, 5, imax(1000.0));

    return 0;
}

int imax(n, m)
int n, m;
{
    return (n > m ? n: m);
}

the output:

enter image description here

What I can't understand is why the last three print statements print same words! I know I am do a test for researching what will happen when the declaration of a function using old style which do not care the formal parameters' type. In the context, I design four cases which the calling function's actual arguments do not match with the requirement of the called function's formal parameters.

I know this is relevant to the mechanism of the stack in C. And I try my best to search why. In my opinion, the last three print statements should behave different. In fact, I think the statement imax(3.0, 1000.0) may be same with imax(3.0) or imax(1000.0) but it's impossible be same with both!

Prostagma
  • 1,756
  • 9
  • 21
Melvin Levett
  • 341
  • 2
  • 3
  • 11

3 Answers3

4
int imax();

and

int imax(n, m)
int n, m;
{
    return (n > m ? n: m);
}

is an ancient style of C code. Don't use it, as one of it's problems is it doesn't do any function argument checking.

The proper standardized code would be

int imax( int n, int m)

and

int imax( int n, int m)
{
    return (n > m ? n: m);
}
Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
2

Your code is not conforming to C99 or C11 standard. Don't use anything older (like K&R C).

This is wrong or at least undefined behaviour and you should be scared of it.

You really should have a prototype (in C99 or C11 style) for every function, that is you need to have a declaration of every used function.

(In old C89 or K&R C, that was not mandatory; but today you should code in C99 at least)

Actually you should code:

#include <stdio.h>

int imax(int, int);

int main(void)
{ 
    printf("%zu %zu\n", sizeof(int), sizeof(double));
    // WRONG CODE BELOW: the compiler should reject it or emit warnings.
    printf("The maximum of %d and %d is %d.\n", 3, 5, imax(3));
    printf("The maximum of %d and %d is %d.\n", 3, 5, imax(3.0, 1000.0));
    printf("The maximum of %d and %d is %d.\n", 3, 5, imax(3.0));
    printf("The maximum of %d and %d is %d.\n", 3, 5, imax(1000.0));

    return 0;
}

int imax(int n, int m)
{
    return (n > m ? n: m);
}

and a recent compiler would reject that code. Be sure to compile with all warnings enabled, e.g. gcc -Wall -Wextra -g)

What really happens (on a mismatch between declared function signature and incorrect call) depends upon the ABI and the calling conventions. Today, on x86-64 with SVR4/Linux ABI, some arguments are passed thru registers. So even if you forced the compiler (e.g. by casting some function address to a function pointer and using that) what would happen is undefined and implementation specific.

If you book shows K&R C code, you need to use a better book showing C99 code (or C11). Bookmark also some C reference site (for C99 at least).

Read the C11 standard, e.g. n1570.

In 2018, you should avoid coding in K&R C (unless forced to).

I know this is relevant to the mechanism of the stack in C.

This is a misconception. Implementations are not required to have a call stack. And most recent implementations don't pass every argument on the stack (most calling conventions may use registers, details are implementation specific).

On Ubuntu, I recommend compiling with gcc -Wall -Wextra -g -all warnings and debug info-. And probably eliciting the language standard, e.g. by adding -std=c99 or -std=gnu11. When you want to benchmark, enable more compiler optimizations (e.g. with -O2)

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 1
    I'm not so sure in this case - OP posted ancient K&R-style C, without a function prototype. – Andrew Henle Apr 12 '18 at 09:22
  • @Basile Starynkevitch In the book that I am reading now - C primer plus, the author explain the result of the program's execution vaguely, he just slightly explain that the stack transport the args. So I try to understand the output following the thought of the author's more far. So the question breaks into. I try my best to understand, but I really can't. Because the last three print statements should print different in my opinion. – Melvin Levett Apr 12 '18 at 09:51
  • **Change your book. It is obsolete.** Get a better book, and refer to the standard once you have learned a bit of C. Don't spend efforts in learning some obsolete language (unless you are forced to). Most of Linux free software uses C99 at least. – Basile Starynkevitch Apr 12 '18 at 09:51
  • Read also more about undefined behaviour. It is tricky, and important to understand. – Basile Starynkevitch Apr 12 '18 at 10:01
  • 1
    @Basile Starynkevitch. Thank you for your answer sincerely. – Melvin Levett Apr 12 '18 at 10:26
2

Edit : To be more precise, it's because you implement your function with the K&R style. Change "int imax(n , m)" to "int imax(int n, int m)" too.

you have to correct the prototype of imax too because function without anything in the parenthesis allow this kind of thing.

When there is nothing in the parameter of the function, the function can take any number of argument. but this is useless and dangerous, since it can lead to security flaw. It's useless because you can't retrieve the correct amount of argument, and it's a security flaw because you can put any number of argument and this can smash your calling stack.

Just take

int imax();

and transform in

int imax(int n, int m);

Now, you function must take two parameter, and compilation will fail if it's not the case.

If you want a function that have no argument, put void in it to ensure that the function can't called with argument

int funcname(void);
Tom's
  • 2,448
  • 10
  • 22
  • 1
    No! **NEVER** add prototypes for functions defined in K&R style. K&R style functions expect arguments to undergo default argument promotions, which function prototypes prevent. – Andrew Henle Apr 12 '18 at 09:26
  • ? Why ? i'm not fluent with the K&R style because it's really old, but I supposed that argument checking was do anyway, no ? – Tom's Apr 12 '18 at 09:27
  • Thank you for your answer sincerely. I know what you said. But what I want to konw is why the output is different from what I thought. – Melvin Levett Apr 12 '18 at 09:28
  • @AndrewHenle Oh ? That's why the name and the type of argument are separated ? To allow undergo argument promotions ? That seem a little dangerous to me, are there a good reasons to do that ? – Tom's Apr 12 '18 at 09:31
  • 1
    @Tom's - No. K&R-style does absolutely no argument checking, and all arguments [undergo default argument promotion](https://stackoverflow.com/questions/1255775/default-argument-promotions-in-c-function-calls). Adding a prototype to a function that's still defined in K&R style breaks things. Ever wonder why `open()` can be called as `open( const char *filename, int flags )` *or* `open( const char *filename, int flags, mode_t mode )`? Because original K&R C didn't check arguments at all, For the `open()` call, POSIX had to hack around legacy code to force-fit `open()` into a "vararg" function. – Andrew Henle Apr 12 '18 at 09:32
  • @Tom's And yes, it's dangerous. Why is why K&R C is pretty much dead. – Andrew Henle Apr 12 '18 at 09:33
  • Thank you for your answer sincerely. I know what you said. But what I want to konw is why the output is different from what I thought. Because without the formal paras checking, the calling for the called function can use error actual args. Yes, it's true because of the old declaration style which is before ANSI standard. But from the viewpoint of arguments push stack, I think that the output should behave like what I said in my question. – Melvin Levett Apr 12 '18 at 09:34
  • @AndrewHenle Wow. Well, the more you know ... I recently cross the road of "open" function and I was surprised that open can behave like an overloaded function. Then I saw the vararg thing and still be a little puzzled. Thank for explaining me where this come from. – Tom's Apr 12 '18 at 09:39
  • `int imax();` is a declaration, not a prototype – M.M Apr 12 '18 at 09:55
  • Huuuu, maybe it's because I'm not english, but it seem top me that in order to declare a function, you use his prototype. No ? – Tom's Apr 12 '18 at 10:52