Struktury i C++
Przeładowanie funkcji w C++

W C++ można zdefiniować kilka funkcji o tej samej nazwie

void f();
int f(int n);
double f(double x, double y);

Co więc oznaczałoby:

call f

C++: kodowanie nazw funkcji
// Funkcja 
 
void f();
void f(int x);
void f(double x);
void f(int * x);
double f(int x, double y);
double f(int (*fun)(int), int);
void Klasa::f(int i, double t);
static void Klasa::f(int i);
; Nazwa
 
_Z1fv
_Z1fi
_Z1fd
_Z1fPi
_Z1fid
_Z1fPFiiEi
_ZN5Klasa1fEid
_ZN5Klasa1fEi

Nazwa jest zależna od systemu, kompilatora. Reguły są dość skomplikowane.

Zamiast zgadywać nazwę można zobaczyć jaką nazwę używa kompilator.

g++ -S plik.cpp -o plik.asm
Powyższa komenda przetłumaczy plik C++ na plik asemblerowy w formacie GAS.

Jeżeli chcemy format bardziej podobny do NASMa to należy dodać opcję -masm=intel.

g++ -masm=intel -S plik.cpp -o plik.asm
C++: kodowanie nazw funkcji
void f();                       // 1
void f(int x);                  // 2 
void f(double x);               // 3
void f(int * x);                // 4
double f(int x, double y);      // 5   
 
class Klasa {
  public:
    void f(int i, double t);    // 6
    static void f(int i);       // 7
};
 
int main(){
   int x=1;
   double y=1.1;
   f();
   f(x);
   f(y);
   f(&x);
   f(x,y);
   Klasa k;
   k.f(x,y);
   Klasa::f(x);
}
 
main:
	push	ebp
	mov	ebp, esp
 
	and	esp, -16
	sub	esp, 32
	mov	DWORD PTR [esp+24], 1
	fld	QWORD PTR .LC0
	fstp	QWORD PTR [esp+16]
	call	_Z1fv                  ; (1)
 
	mov	eax, DWORD PTR [esp+24]
	mov	DWORD PTR [esp], eax
	call	_Z1fi                  ; (2)
 
	fld	QWORD PTR [esp+16]
	fstp	QWORD PTR [esp]
	call	_Z1fd                  ; (3)
 
	lea	eax, [esp+24]
	mov	DWORD PTR [esp], eax
	call	_Z1fPi                 ; (4)
 
	mov	eax, DWORD PTR [esp+24]
	fld	QWORD PTR [esp+16]
	fstp	QWORD PTR [esp+4]
	mov	DWORD PTR [esp], eax
	call	_Z1fid                 ; (5)
 
	fstp	st(0)
	mov	eax, DWORD PTR [esp+24]
	fld	QWORD PTR [esp+16]
	fstp	QWORD PTR [esp+8]
	mov	DWORD PTR [esp+4], eax
	lea	eax, [esp+31]
	mov	DWORD PTR [esp], eax
	call	_ZN5Klasa1fEid         ; (6)
 
	mov	eax, DWORD PTR [esp+24]
	mov	DWORD PTR [esp], eax
	call	_ZN5Klasa1fEi          ; (7)
 
	mov	eax, 0
	leave
	ret
.LC0:
	.long	2576980378
	.long	1072798105

Przykładowy plik C++
Odpowiadający mu plik asemblerowy (GAS)

Zwracanie struktury przez wartość vs. przez wskaźnik

Porównajmy dwa sposoby inicjowania struktury

typedef struct {
  int a;
  int b;
  char c;
  double d;
  double e;
} Dane;
Dane init(){
  Dane d;
  d.a = 1;
  d.b = 2;
  d.c = A;
  d.d = 1.244;
  d.e = 2.345;
  return d;
}
int main(){
   Dane d = init();
   return 0;
}
void init(Dane * d){
  d->a = 1;
  d->b = 2;
  d->c = 'A';
  d->d = 1.244;
  d->e = 2.345;
}
int main(){
   Dane d;
   init(&d);
   return 0;
}

Niebezpieczeństwo w pierwszym kodzie polega na tym, że pomimo iż do funkcji jest przekazywany adres struktury d, to w funkcji zostanie utworzony lokalny obiekt i na końcu funkcji będzie potrzebne skopiowanie go.
W C++ nie mamy w funkcji init jawnego dostępu do obiektu w którym zwracamy wartość.

Obecne kompilatory potrafią wygenerować wydajny kod nawet bez optymalizacji.

Pierwszy program (Pokaż/ukryj kod)

 
#include <stdio.h>
typedef struct {
  int a;
  int b;
  char c;
  double d;
  double e;
} Dane;
 
Dane init(){
  Dane d;
  d.a = 1;
  d.b = 2;
  d.c = 'A';
  d.d = 1.244;
  d.e = 2.345;
  return d;
}
 
int main(){
   Dane d = init();
   return 0;
}
 

Kod asemblerowy wygenerowany przez gcc (Pokaż/ukryj kod)
	.file	"zwracanie_structury.cpp"
	.intel_syntax noprefix
	.text
.globl _Z4initv
	.type	_Z4initv, @function
_Z4initv:
.LFB0:
	.cfi_startproc
	push	ebp
	.cfi_def_cfa_offset 8
	mov	ebp, esp
	.cfi_offset 5, -8
	.cfi_def_cfa_register 5
	sub	esp, 32
	mov	eax, DWORD PTR [ebp+8]
	mov	DWORD PTR [eax], 1
	mov	DWORD PTR [eax+4], 2
	mov	BYTE PTR [eax+8], 65
	fld	QWORD PTR .LC0
	fstp	QWORD PTR [eax+12]
	fld	QWORD PTR .LC1
	fstp	QWORD PTR [eax+20]
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret	4
	.cfi_endproc
.LFE0:
	.size	_Z4initv, .-_Z4initv
.globl main
	.type	main, @function
main:
.LFB1:
	.cfi_startproc
	push	ebp
	.cfi_def_cfa_offset 8
	mov	ebp, esp
	.cfi_offset 5, -8
	.cfi_def_cfa_register 5
	sub	esp, 36
	lea	eax, [ebp-28]
	mov	DWORD PTR [esp], eax
	call	_Z4initv
	sub	esp, 4
	mov	eax, 0
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.section	.rodata
	.align 8
.LC0:
	.long	2336462209
	.long	1072949100
	.align 8
.LC1:
	.long	1546188227
	.long	1073922703
	.ident	"GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
	.section	.note.GNU-stack,"",@progbits
 

Drugi program (Pokaż/ukryj kod)

 
#include <stdio.h>
typedef struct {
  int a;
  int b;
  char c;
  double d;
  double e;
} Dane;
 
void init(Dane * d){
  d->a = 1;
  d->b = 2;
  d->c = 'A';
  d->d = 1.244;
  d->e = 2.345;
}
 
int main(){
   Dane d;
   init(&d);
   return 0;
}
 

Kod asemblerowy wygenerowany przez gcc (Pokaż/ukryj kod)
	.file	"zwracanie_structury3.cpp"
	.intel_syntax noprefix
	.text
.globl _Z4initP4Dane
	.type	_Z4initP4Dane, @function
_Z4initP4Dane:
.LFB0:
	.cfi_startproc
	push	ebp
	.cfi_def_cfa_offset 8
	mov	ebp, esp
	.cfi_offset 5, -8
	.cfi_def_cfa_register 5
	mov	eax, DWORD PTR [ebp+8]
	mov	DWORD PTR [eax], 1
	mov	eax, DWORD PTR [ebp+8]
	mov	DWORD PTR [eax+4], 2
	mov	eax, DWORD PTR [ebp+8]
	mov	BYTE PTR [eax+8], 65
	mov	eax, DWORD PTR [ebp+8]
	fld	QWORD PTR .LC0
	fstp	QWORD PTR [eax+12]
	mov	eax, DWORD PTR [ebp+8]
	fld	QWORD PTR .LC1
	fstp	QWORD PTR [eax+20]
	pop	ebp
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	_Z4initP4Dane, .-_Z4initP4Dane
.globl main
	.type	main, @function
main:
.LFB1:
	.cfi_startproc
	push	ebp
	.cfi_def_cfa_offset 8
	mov	ebp, esp
	.cfi_offset 5, -8
	.cfi_def_cfa_register 5
	sub	esp, 36
	lea	eax, [ebp-28]
	mov	DWORD PTR [esp], eax
	call	_Z4initP4Dane
	mov	eax, 0
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.section	.rodata
	.align 8
.LC0:
	.long	2336462209
	.long	1072949100
	.align 8
.LC1:
	.long	1546188227
	.long	1073922703
	.ident	"GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
	.section	.note.GNU-stack,"",@progbits
 

Referencje

Przekazywanie argumentów przez referencje w C++ jest równoważne w assemblerze przekazywaniu wskaźnika zmiennej.

void fun(int * x)
{
   *x = 1;
}
 
 
 
 
void fun(int & x)
{
   x = 1;
}
 
 
 
 
int main()
{
   int x = 0;
   fun(&x);
   fun(x);
}
_Z3funPi:
   push  ebp
   mov   ebp, esp
   mov   eax,[ebp+8]
   mov   [eax], 1
   pop   ebp
   ret
 
_Z3funRi:
   push  ebp
   mov   ebp, esp
   mov   eax, [ebp+8]
   mov   [eax], 1
   pop   ebp
   ret
 
global main
main:
 
   push	ebp
   mov	ebp, esp
   sub	esp, 20
   mov	[ebp-4], 0
 
   lea	eax, [ebp-4]
   mov	[esp], eax
   call	_Z3funPi
 
   lea	eax, [ebp-4]
   mov	[esp], eax
   call	_Z3funRi
 
   mov	eax, 0
   leave
   ret

Referencje są tylko udogodnieniem w sposobie zapisu, zwłaszcza przy przeciążaniu operatorów np. a + b zamiast &a + &b .

Funkcje inline

Funkcje inline są z założenia krótkimi funkcjami, dla których w miejscu wywołania wkleja się ich ciało zamiast robić skok do podprogramu.

Funkcje te miały zastąpić makra preprocesora.

Funkcje inline poprzedzamy w C++ słowem kluczowym inline. Jest to jednak tylko sugestia dla kompilatora. Ciało takiej funkcji kompilator musi znać w momencie kompilacji dlatego najczęściej umieszcza się je w plikach nagłówkowych.

int kwadrat(int x){
	return x*x;
}
 
inline 
int kwadrat_inline(int x){
	return x*x;
}
 
int main(){
   int x = 5, y ;
   y = kwadrat(x);
   y = kwadrat_inline(x2);
}
_Z7kwadrati:
	push  ebp
	mov	  ebp, esp
	mov	  eax, [ebp+8]
	imul  eax, [ebp+8]
	pop	ebp
	ret
 
main:
	push  ebp
	mov	  ebp, esp
	and	  esp, -16
	sub	  esp, 32
 
	mov	  [esp+28], 5
	mov	  eax, [esp+28]
	push  eax
	call  _Z7kwadrati
	mov	  [esp+24], eax
 
	mov	  eax, [esp+28]
	imul  eax, [esp+28]
	mov	  [esp+24], eax
 
	mov	eax, 0
	leave
	ret

W praktyce kompilator GCC w tym przypadku nie inline'ował funkcji, a przy włączonej optymalizacji po prostu obliczał jej wartość

Klasy
class A{
public:
  int x;
  int y;
  void f(){
    x=1;  	
  }
};
int main(){
  A a
  a.y = 3;
  a.f();
}
main:
push	rbp        ; ramka stosu
mov	rbp, rsp
sub	rsp, 16
 
mov	DWORD [rbp-12], 3    ; a.y = 3
 
lea	rax, [rbp-16]  ; przesylamy adres a
mov	rdi, rax       ; jako pierwszy parametr
call	_ZN1A1fEv      ; metody f
 
mov	eax, 0	
leave
ret

Proste dziedziczenie
class A{
public:
  char ma;                       
  void f(){
    cout << "\n f w A \n"  ;  	
  }
};
 
class B : public A {
public:
  int mb; 
  void f(){
    cout << "\n f w B \n" ;
  }
};
 
void check( A * p){
  p->ma = 3;
  p->f();
}
 
 
 
 
_Z5checkP1A:
push	rbp
mov	rbp, rsp
 
mov	rax, rdi
mov	byte [rax], 3
 
call	_ZN1A1fEv
 
leave
ret

Przykład

Polimorfizm
class A{
public:
  char ma;                       
  virtual void f(){
    cout << "\n f w A \n"  ;  	
  }
};
 
class B : public A {
public:
  int mb; 
  void f(){
    cout << "\n f w B \n" ;
  }
};
 
void check( A * p){
  p->ma = 3;
  p->f();
}
 
 
_Z5checkP1A:
push	rbp
mov	rbp, rsp
sub	rsp, 16
 
mov	rax, rdi
mov	BYTE [rax+8], 3
 
mov	rax, [rax]
mov	rdx, [rax]
 
call	rdx
 
leave
ret

Przykład

Tablice funkcji wirtualnych - proste dziedziczenie

Jeżeli klasa bazowa i potomna nie mają funkcji wirtualnych to obiekty nie posiadają wskaźników do tablicy funkcji wirtualnych.

class A {
public:
  int a;
};
 
class B : public A {
public:
  int b;
};
A --> +---------+
      |    a    |
      +---------+
 
B --> +---------+
      |    a    |
      +---------+
      |    b    |
      +---------+

Jeżeli w klasie są obecne funkcje wirtualne to obiekt posiada wskaźnik do tablicy funkcji wirtualnych.

class A {
public:
  int a;
  virtual void v();
};
 
class B : public A {
public:
  int b;
  virtual void w();
          void f();
};
                +-----------------+
                | 0 (top_offset)  |              
  B             ------------------+      typeinfo 
>+--------+     | wsk do typeinfo |----> dla B       
 | vtable |---> +-----------------+       
 +--------+     | * A::v()        |----> A::v(){} 
 |    a   |     +-----------------+
 +--------+     | * B::w()        |----> B::w(){}  
 |    b   |     +-----------------+ 
 +--------+

Jeżeli w klasie B nadpiszemy funkcję v to w vtable zmieni się wskaźnik.

class A {
public:
  int a;
  virtual void v();
};
 
class B : public A {
public:
  int b;
          void v();
  virtual void w();
          void f();
};
                +-----------------+
                | 0 (top_offset)  |              
  B             ------------------+      typeinfo 
>+--------+     | wsk do typeinfo |----> dla B       
 | vtable |---> +-----------------+       
 +--------+     | * A::v()        |--+   A::v(){} 
 |    a   |     +-----------------+  +-> B::v(){} 
 +--------+     | * B::w()        |----> B::w(){}  
 |    b   |     +-----------------+ 
 +--------+
Tablice funkcji wirtualnych
class A {
public:
  int a;
  virtual void v();
};
 
class B {
public:
  int b;
  virtual void w();
};
 
class C : public A, public B {
public:
  int c;
};
                            +-----------------------+
                           |     0 (top_offset)    |
                           +-----------------------+
c --> +----------+         | ptr to typeinfo for C |
      |  vtable  |-------> +-----------------------+
      +----------+         |         A::v()        |
      |     a    |         +-----------------------+
      +----------+         |    -8 (top_offset)    |
      |  vtable  |---+     +-----------------------+
      +----------+   |     | ptr to typeinfo for C |
      |     b    |   +---> +-----------------------+
      +----------+         |         B::w()        |
      |     c    |         +-----------------------+
      +----------+
Tablice funkcji wirtualnych
class A {
public:
  int a;
  virtual void v();
};
 
class B : public A {
public:
  int b;
  virtual void w();
};
 
class C : public A {
public:
  int c;
  virtual void x();
};
 
class D : public B, public C {
public:
  int d;
  virtual void y();
};
                           +-----------------------+
                           |     0 (top_offset)    |
                           +-----------------------+
d --> +----------+         | ptr to typeinfo for D |
      |  vtable  |-------> +-----------------------+
      +----------+         |         A::v()        |
      |     a    |         +-----------------------+
      +----------+         |         B::w()        |
      |     b    |         +-----------------------+
      +----------+         |         D::y()        |
      |  vtable  |---+     +-----------------------+
      +----------+   |     |   -12 (top_offset)    |
      |     a    |   |     +-----------------------+
      +----------+   |     | ptr to typeinfo for D |
      |     c    |   +---> +-----------------------+
      +----------+         |         A::v()        |
      |     d    |         +-----------------------+
      +----------+         |         C::x()        |
                           +-----------------------+
Tablice funkcji wirtualnych
class A {
public:
  int a;
  virtual void v();
};
 
class B : public virtual A {
public:
  int b;
  virtual void w();
};
 
class C : public virtual A {
public:
  int c;
  virtual void x();
};
 
class D : public B, public C {
public:
  int d;
  virtual void y();
};
                                   +-----------------------+
                                   |   20 (vbase_offset)   |
                                   +-----------------------+
                                   |     0 (top_offset)    |
                                   +-----------------------+
                                   | ptr to typeinfo for D |
                      +----------> +-----------------------+
d --> +----------+    |            |         B::w()        |
      |  vtable  |----+            +-----------------------+
      +----------+                 |         D::y()        |
      |     b    |                 +-----------------------+
      +----------+                 |   12 (vbase_offset)   |
      |  vtable  |---------+       +-----------------------+
      +----------+         |       |    -8 (top_offset)    |
      |     c    |         |       +-----------------------+
      +----------+         |       | ptr to typeinfo for D |
      |     d    |         +-----> +-----------------------+
      +----------+                 |         C::x()        |
      |  vtable  |----+            +-----------------------+
      +----------+    |            |    0 (vbase_offset)   |
      |     a    |    |            +-----------------------+
      +----------+    |            |   -20 (top_offset)    |
                      |            +-----------------------+
                      |            | ptr to typeinfo for D |
                      +----------> +-----------------------+
                                   |         A::v()        |
                                   +-----------------------+
Tablice funkcji wirtualnych

Realizacja tablic funkcji wirtualnych zależy od tego jakie są klasy bazowe danej klasy. Jest to też często specyficzne dla danego kompilatora

Dobrą prezentację można znaleźć pod adresem
http://tinydrblog.appspot.com/?p=89001
Lokalna kopia strony

Dodatkowe materiały

Itanium C++ ABI - można tam znaleźć wiele szczegółów implementacyjnych różnych konstrukcji C++.


Tomasz Kapela, Kraków 2011-2020