1

I am trying to analyze what happening to the extra or less parameters that are supplied to the function pointers which are not of compatible size (Less or more arguments).

Consider the below example

#include <stdio.h>
#include <conio.h>

typedef void (*fp)(int a, int b, int c);

void function1(int a, int b) {
    printf("\n Function1: a: %d, b: %d (%d)", a, b, __LINE__);
}

void function2(int a, int b, int c) {
    printf("\n Function1: a: %d, b: %d, c: %d (%d)", a, b, c, __LINE__);
}

void function3(int a, int b, int c, int d) {
    printf("\n Function1: a: %d, b: %d, c: %d, d: %d (%d)", a, b, c, d, __LINE__);
}

int main() {
    fp fp1 = (fp)function1;
    fp fp2 = (fp)function2;
    fp fp3 = (fp)function3;

    fp1(1, 2, 3);
    fp2(4, 5, 6);
    fp3(7, 8, 9);

        getch();

    return 0;
}

It can be observed that fp1 & fp3 take different parameter sizes (arguments) than the function pointer. But still i didn't observe any undefined behavior while executing the program.

Output of the program is

Function1: a: 1, b: 2 (7)
Function1: a: 4, b: 5, c: 6 (11)
Function1: a: 7, b: 8, c: 9, d: 4200926 (15)

So, when are calling a function we push the address and the parameters to the stack according to the calling convention and then are popped out. In the above case when we push parameters to the stack and popping out them ,we are popping out one less element which actual has to corrupt the return address (Same in case of fp3), but all the code is executing properly.

In this stack overflow question there is no mismatch in the function arguments. So, i am not able to find answer for my question.

From the wikipedia , it has been mentioned that callee/caller need to cleanup the stack like this

Callee clean-up[edit] When the callee cleans the arguments from the stack it needs to be known at compile time how many bytes the stack needs to be adjusted. Therefore, these calling conventions are not compatible with variable argument lists, e.g. printf(). They may be, however, more space efficient, as the code needed to unwind the stack does not need to be generated for each call. Functions which utilize these conventions are easy to recognize in ASM code because they will unwind the stack prior to returning. The x86 ret instruction allows an optional 16-bit parameter that specifies the number of stack bytes to unwind before returning to the caller. Such code looks like this:

ret 12

But i didn't observe anything like that in the dis-assembly code of the above program. (pasted below for function1)

--------------------------------------------------------------------------------
18  int main() {
0x004013C7  push   %ebp
0x004013C8  mov    %esp,%ebp
0x004013CA  and    $0xfffffff0,%esp
0x004013CD  sub    $0x20,%esp
0x004013D0  call   0x401a00 <__main>
19      fp fp1 = (fp)function1;
0x004013D5  movl   $0x401334,0x1c(%esp)
20      fp fp2 = (fp)function2;
0x004013DD  movl   $0x40135e,0x18(%esp)
21      fp fp3 = (fp)function3;
0x004013E5  movl   $0x40138f,0x14(%esp)
23      fp1(1, 2, 3);
0x004013ED  movl   $0x3,0x8(%esp)
0x004013F5  movl   $0x2,0x4(%esp)
0x004013FD  movl   $0x1,(%esp)
0x00401404  mov    0x1c(%esp),%eax
0x00401408  call   *%eax
24      fp2(4, 5, 6);
0x0040140A  movl   $0x6,0x8(%esp)
0x00401412  movl   $0x5,0x4(%esp)
0x0040141A  movl   $0x4,(%esp)
0x00401421  mov    0x18(%esp),%eax
0x00401425  call   *%eax
25      fp3(7, 8, 9);
0x00401427  movl   $0x9,0x8(%esp)
0x0040142F  movl   $0x8,0x4(%esp)
0x00401437  movl   $0x7,(%esp)
0x0040143E  mov    0x14(%esp),%eax
0x00401442  call   *%eax
27      getch();
0x00401444  call   0x401c40 <getch>
29      return 0;
0x00401449  mov    $0x0,%eax
30  }
0x0040144E  leave
0x0040144F  ret

--------------------------------------------------------------------------------
6   void function1(int a, int b) {
0x00401334  push   %ebp
0x00401335  mov    %esp,%ebp
0x00401337  sub    $0x18,%esp
7       printf("\n Function1: a: %d, b: %d (%d)", a, b, __LINE__);
0x0040133A  movl   $0x7,0xc(%esp)
0x00401342  mov    0xc(%ebp),%eax
0x00401345  mov    %eax,0x8(%esp)
0x00401349  mov    0x8(%ebp),%eax
0x0040134C  mov    %eax,0x4(%esp)
0x00401350  movl   $0x403024,(%esp)
0x00401357  call   0x401c78 <printf>
8   }
0x0040135C  leave
0x0040135D  ret

So, will anyone please guide/help me in finding the answer.

Thanks.

Community
  • 1
  • 1
Suman
  • 4,221
  • 7
  • 44
  • 64
  • 9
    'But still i didn't observe any undefined behavior while executing the program.'. This indicates you don't understand undefined behaviour. Any behaviour **at all** could be undefined behaviour. Undefined behaviour means the behaviour is undefined, not that the program must do something unexpected. It's common English words, I don't know how to say it any clearer. – john Dec 01 '13 at 15:21
  • I guess there is no need to unwind the stack, since the space required for the arguments is taken into account at the line: 0x004013CD sub $0x20,%esp – Jasper Dec 01 '13 at 16:18
  • @qwrrty Hmmm - C++ tag was removed - I"m not seeing anything that doesn't work in C++ – Glenn Teitelbaum Dec 01 '13 at 16:27
  • @GlennTeitelbaum There isn't anything that is specific to C++ about this code, and indeed, a C++ application would almost certainly handle this with polymorphism rather than casting function pointers. – Tim Pierce Dec 01 '13 at 16:31

2 Answers2

4

The problem with undefined behavior is that it can do what you want, it just isn't guarenteed to do the same thing in all cases. (Even given a single compiler on a specific archetecture). There are standards and implementation specific guarentees as to defined behavior. And it is possible that you may never find what you feel is unexpected because the implementer has decided to do something reasonable but not guarentee it.

In this case putting too many things on the stack can be harmeless as the callee only refers to the first N of X pushed.

When function3 is called it gets 4200926 for d. This is NOT defined behavior, nor would any value, a seg fault or anything else be defined behavoiur. It is simply undefined behavior.

It is not always the callee that cleans up the stack, the caller can recover the stack to its value prior to the call, this is not a defined behavior. Either method is acceptable, the functionality of cleaning up the stack is purely implementation defined, and implementers can even allow flags to change this.

Glenn Teitelbaum
  • 10,108
  • 3
  • 36
  • 80
3

Without giving any lengthy explanation my answer is; It simply invokes undefined behavior.

n1570: 6.5.2.2 Function calls(P2):

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.If the number of arguments does not equal the number of parameters, the behavior is undefined.1...

In such case you may get anything; either expected or unexpected output. Sometimes you may get segmentation fault or sometimes program may get crash.


1. Emphasis is mine.

Community
  • 1
  • 1
haccks
  • 104,019
  • 25
  • 176
  • 264
  • So, does it mean that in this program its not crashing (fortunately), else usually it will be undefined behavior? I mean it may or may not crash. – Suman Dec 02 '13 at 04:59
  • Yes. Exactly. Anything could happen. – haccks Dec 02 '13 at 05:05