0

I read about early and late binding in C++:

int add (int x, int y)
{
  return x+y;
}

int main()
{
   int a=add(5,6);//early binding
   int (*p_add)(int,int)=add;

   int b=p_add(5,19);
}

Why can't int b=p_add(5,19) be resolved at compile time? We all know it is associated with the add function at compile time. Then why can't we resolve it at compile time same as add function? My problem is that if i know add(x,y) at compile time then I may predict p_add at compile time too.

Nick
  • 4,820
  • 18
  • 31
  • 47
Aayush Neupane
  • 1,066
  • 1
  • 12
  • 29
  • Sure, in THIS example, the compiler COULD see where `p_add` is initialized and optimize it, if it wanted to. But move `p_add` to another context where that is not possible, like as a parameter to another function, and see what happens. – Remy Lebeau Mar 01 '20 at 02:47
  • @RemyLebeau so, does late binding take place in above example? – Aayush Neupane Mar 01 '20 at 02:53
  • 2
    The terms "early binding" and "late binding" do not appear anywhere in the C++ standard. There are no such concepts in the C++ language. So the question "does late binding take place" is unanswerable, as the term "late binding" is not defined. – Igor Tandetnik Mar 01 '20 at 04:26
  • There is nothing resembling an opportunity for 'late binding' here. That kibnd of thing only comes into play for virtual methods. This is all early-bound by the compiler. If what you're really asking is about eliminating the dereference via `p_add`, make it `const` to give the compiler half a chance. – user207421 Mar 01 '20 at 05:58

1 Answers1

2

Here's what gcc and Clang produce for your code as it stands right now:

main:                                   # @main
    xor     eax, eax
    ret

code on Godbolt

So in this case, we don't really have either early or late binding. Rather, we have no binding to the function at all--you didn't use the result you got from calling the function (either directly or via a pointer), so the compiler simply didn't generate any code to call the function at all.

We can repair that with code on this order:

#include <iostream>

int add (int x, int y)
{
  return x+y;
}

int main()
{
   int a=add(5,6);//early binding
   int (*p_add)(int,int)=add;

   int b=p_add(5,19);
   std::cout << b;
}

In this case, the compiler still detect that the result of the function doesn't depend on anything at compile time, so it computes the value at compile time, and prints it out as a constant:

mov esi, 24
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)ant:

Code on Godbolt

So, we still don't have any real "binding" to the function. Let's have it use inputs it won't know until run-time:

#include <iostream>
#include <cstdlib>

int add (int x, int y)
{
  return x+y;
}

int main()
{
    int x1 = rand();
    int x2 = rand();

   int a=add(x1, x2);//early binding
   int (*p_add)(int,int)=add;

   int b=p_add(x1,x2);
   std::cout << b;
}

This source produces the following object code:

call rand
mov ebx, eax
call rand
mov edi, OFFSET FLAT:_ZSt4cout
lea esi, [rbx+rax]
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)

The compiler is still aware that the pointer consistently points to one particular function, so even though the source code shows the function being called via a pointer, in the object code we don't call the function via a pointer...and in fact, we still don't call the function at all. Instead, the code for the body of the function has been generated inline.

To get an actual function call via a pointer, we can have a pointer that refers to either of two different functions, and it won't be apparent until run-time which of the two to use in a particular case. For example:

#include <iostream>
#include <cstdlib>

int add (int x, int y)
{
  return x+y;
}

int sub(int x, int y) { 
    return x-y;
}

int main()
{
    int x1 = rand();
    int x2 = rand();

    int z = rand() % 2;

   int (*p_add)(int,int) = z ? add : sub;

   int b=p_add(x1,x2);
   std::cout << b;
}

This (finally!) make the call via a pointer actually happen as a call via a pointer:

  call rand
  mov edx, OFFSET FLAT:sub(int, int) ; start by assuming we'll subract
  mov esi, r12d
  mov edi, ebp
  test al, 1                         ; then see if we have an odd or even number
  mov eax, OFFSET FLAT:add(int, int)
  cmove rax, rdx                     ; if necessary, point to add
  call rax                           ; and finally call the function via the pointer
  mov edi, OFFSET FLAT:_ZSt4cout
  mov esi, eax
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)

Code on Godbolt

Summary

If it's apparent at compile time what function will be called, the compiler probably won't generate code to call the function via a pointer, even if that's what the source code shows.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111