[linux-l] Re: Function Pointer in C

Steffen Dettmer steffen at dett.de
Mi Jun 15 00:36:23 CEST 2005


Hi,

stoplere gerade mal wieder über die ML und antworte mal einfach :)

* Rocco Rutte wrote on Sat, May 28, 2005 at 07:05 +0000:
> * Steffen Dettmer [05-05-28 03:18:57 +0200] wrote:
> >* Rocco Rutte wrote on Wed, May 18, 2005 at 20:00 +0000:
> 
> >Na ja, und dann stellt man fest, dass Dein C-Code ja eigentlich das
> >gleiche ist, was Java macht, nur das Java "Luxus-Syntax" hat (und schon
> >fast ein halbes C++ ist!) 
> 
> Moment. C wird ja gern mal als "Makroassembler" bezeichnet, hat aber 
> IMHO einen Vorteil. Wenn ich das so schreibe, dann weiss ich woran ich 
> bin. In C++ und Java weiss ich das nicht mehr. 

Aber warum weisst Du das denn nicht? Garbage Collection ist ein Bespiel,
OK, aber nicht in C++ oder in Perl.

> Bei Java nervt mich zum Beispiel die Garbage Collection ja nur, weil
> ich keinen Einfluss darauf habe, obwohl ich es besser weiss als der
> Compiler/die VM.

Na ja, damit könnte ich noch leben - solange es funktioniert. Aber wenn
man CORBA etc. pp. verwendet, und plötzlich outOfMemoryExceptions
bekommt, na ja.

> Wenn ich Sachen via Callbacks löse, dann weiss ich genau, dass der ganze 
> Code zur Behandlung nur einmal da ist, also nur einmal im Speicher ist. 
> Bei Java, zum Beispiel, habe ich keinen Einfluss darauf, wie die 
> Vererbung organisiert wird. Wenn ich eine Klasse Foo von Bar ableite 
> und Bar eine Instanzmethode foobar() hat, die vererbt wird, dann gibt es 
> ja zwei Möglichkeiten: (1) sie wird nicht überschrieben und (2) sie wird 
> überschrieben. Im Fall (1) brauche ich das Original nur 1x im Speicher, 
> im Fall (2) jede Inkarnation nur einmal. 

?

Der Code ist sooft im Speicher, wie im C/C++ Modul (mal von inline
abgesehen, aber bei #define ist's ja auch so). Zumindestens in C++ ist
ziemlich genau definiert, wie's funktioniert. Zum Beispiel, dass die
Foo Instanz immer das eingene foobar verwendet, wenn kein "virtual" dran
steht. In Java steht's sozusagen immer dran; da hast Du Recht, für viele
kleine Serviceinstanzen und Methoden ist das bissel langsamer, klar.
Aber für'n Text-Parser...

> Wie Java das jetzt organisiert  und optimiert, weiss ich nicht; für C:
> wenn ich 10 verschiedene Instanzen der Klasse Foo benutze, dann
> brauche ich sowas wie Instanzmethoden nicht, 

Es gibt in C/C++ keine Instanzmethoden (von direktem Memory-Patching mal
abgesehen). Es ist wirklich das gleiche wie in C, nur das das "this"
oder "self" implizit übergeben wird und das man die Funktion nicht
direkt, sondern über einen indirekten Zeiger aufruft (struct-Eintrag).
Der Code wird ja auch beim Compilieren von Bar erzeugt; hier kann der
Compiler ja auch gar nicht wissen, ob's denn nu ein Foo geben wird, was
irgendwas überschreibt. Und Code zu erzeugen, dessen Grösse zur Laufzeit
erst bekannt wird, geht so ja auch nicht (mal von libdl abgesehen ;)).

> weil die pro Inkarnation eh konstant sind, so dass sie auch nur einmal
> da sein müssen.

Sind sie auch nur. Ein gutes C Modul mit Funktionspointern ist ziemlich
genau eine C++ Klasse, nur dass man es alles hinschreiben muss, was
sonst der Compiler macht (insbesondere casts).

> Was ich eigentlich nur sagen will, ist, dass ich in C in dem Bereich
> die Kontrolle darüber habe, nur die pro "Objekt" dynamischen Sachen
> wirklich dynamisch zu gestalten und den Rest halt statisch, "statisch"
> im Sinne von "einmal pro Programmlauf". 

Ebenso in C++.

> In Java ist mein nicht-praktikabler Denksatz daher meist auch, Klassen
> nur als Wrapper für dynamische Daten zu nutzen und anderswo Funktionen
> darauf stets "static" zu deklarieren ("static" im Java Sinn) 

(ja, der ist in C++ der gleiche)

Das static nimmt ja nur das implizite "this" weg, also der erste
Parameter. Dafür musst Du sicherlich irgendeine Referenz auf die
eigentlichen Daten übergeben, summa sumarum das gleiche.

> (es kann ja sein, dass der Compiler/die VM das wegoptimiert, auf das
> genutzte Minimum reduziert und dann ausführt; ich habe aber noch keine
> ausführlichere Doku dazu gefunden).

Ich glaub, das geht so einfach nicht, weil man über reflection oder
libdl ja Code nachladen könnte, der diesen benutzt (überschreibt und
aufruft oder so), was man ja noch nicht wissen kann. Könnte man
natürlich machen, wenn man das später wieder korrigieren kann, aber ich
glaube (nicht wissen ;)), das Java das nicht so macht. C++ hingegen
schmeisst beim statischen Linken den Kram i.d.R. weg; C++ Libs möchte
man vermutlich auch nicht libdl-nachladen...

> Wenn man sich zum Beispiel javadoc-ähnliche Programme anguckt und
> ihnen sagt, sie sollen bitte stets auch geerbte Methoden listen und
> dann das wirklich mal für ein Objekt macht, dann kommt da tonnenweise
> Zeugs alleine schon aus java.lang.Object. Plus dem, was im
> Vererbungsbaum noch dazwischen gemacht wird. Das "fertige" Objekt kann
> dann zwar relativ viele Sachen, die auch nur jeweils einmal
> implementiert wurden; es widerstrebt aber meinem Lieblingsansatz alles
> so minimal wie möglich zu halten.

mmm... Der Ansatz klingt nicht schlecht. Ich persönlich ziehe KISS (keep
it simple, stupid) vor. Das kann heissen, dass man lieber etwas mehr
macht, wenns einfacher wird. 

Über die Java-Interna kann ich nicht viel sagen. Aber in C++ ist das so,
dass die VMT im Object ein Zeiger ist, egal, wieviele Methoden da drin
stehen. Die VMT ist selbst auch statisch (also code), die gibt's also
nur einmal (je Klasse/Typ, nicht je Instanz!). Damit bleibt
sizeof(myInstance) schön klein. Guck:

=====8<----------------------------------------------------------
#include <iostream>

// Eine struct mit einem int in gcc auf i386 4 bytes
struct SimpleStruct
{
   int a_;
};
// methode für SimpleStruct, nur code
int SimpleStructGetA(struct SimpleStruct *self)
{
        return self->a_;
}

// Das gleiche in C++, auch nur 4 byte!
class Simple
{
   public:
      Simple(int a) : a_(a) { }
      int getA(void) { return a_; } // return this->a_;
   private: int a_;
};


// Polymorphy braucht einen (!) VMT-Zeiger und den int, also 4+4 byte.
//   (Die Anzahl der Methoden oder Instanzen ist egal dabei)
class Poly
{
   public:
      Poly(void) { }
      // ein virtual erfordert VMT, kostet einmalig (je Klasse) 4 byte
      virtual void aMethod(void) { } // macht: vmt->aMethod(this)
      // die kosten nix extra:
      virtual void anotherMethod(void) { }
      virtual void anotherMethod2(void) { }
      virtual void anotherMethod3(void) { }
      virtual ~Poly(void) { }
   private: 
      // unsichtbar:
      // struct vmt_s *vmt_;
      int a_;
};

int
main (int argc, char *argv[])
{
   std::cout << "sizeof(SimpleStruct): " << sizeof(SimpleStruct) // 4
             << "\nsizeof(Simple): " << sizeof(Simple) // 4
             << "\nsizeof(Poly): " << sizeof(Poly) // 8
             << std::endl;
   return(0);
}
--------------------------------------------------------->8======
$ g++ x3.cc -Wall -o x3 && ./x3
sizeof(SimpleStruct): 4
sizeof(Simple): 4
sizeof(Poly): 8

Simple als Klasse kostet nichts extra. Die Anzahl der virtuellen
Funktionen ist auch egal. Wenn Du nicht virutell nimmst, ist's gleich
"teuer" wie "static" (nämlich gratis :)). Sonst eben einmalig 4 byte für
"typeinformationen" verbraten, bei sinnvollen structs dann schnell <1%
der Grösse :)

> >dumme, einfach Module verwendet man öfter wieder. Ich hab lieber viele
> >kleine dumme Bausteine. Wie oben die hash_map. Macht hashmap und nicht
> >mehr. Kann man immer mal gebrauchen. Kann man ja beerben (in C natürlich
> >bissel fummelig durch diese castereien).
> 
> ACK.
> 
> Eigentlich ist das casten ja "nur" hässlich, begegnet mir in Java aber 

Java ist ja auch nicht so sauber wie C++ (aber IMHO etwas sauberer als
C), finde ich. Aber hin- und wieder muss man ja eh casten. In C++ ist
das toll: es gibt 

// Die "prototypen" sind ausgedacht, weiss nicht, wie man sowas richtig
//    schreibt
static_cast<typename T>(T *);
reinterpret_cast<typename T>(T *);
dynamic_cast<typename T>(T *);

und den C-style-Cast (AFIAK so ziemlich das gleiche wie
reinterpret_cast).

> auch regelmäßig und ist mir lieber als Templates/Generics, in denen es
> sich zwar schön einfach denken lässt, von denen ich aber in obigem
> Sinne wieder keine Ahnung habe...

:-)

Ja, ich muss auch immer nachgucken, wenn's an STL geht, aber der Kram
ist wirklich toll. Es gibt z.B. ne Funktion "find", klingt dämlich, aber
ist praktisch, so kann man z.B. iteratoren direkt "zuweisen". In Java
ist das IMHO umständlich (und/oder nicht typsicher). Natürlich kann man
alles erweitern etc. pp.

> >Na ja, hat alles so seine Vor- und Nachteile...
> 
> Das ist doch mal ein innovatives Schlusswort ;-)

:-)

Ich glaube, wenn Du mal einen Hammerkurs in STL bekommen würdest, also
incl. der "schwierigen Sachen" und dabei ne Weile mit Klassen und
Templates arbeitet würdest (vielleicht ein Jahr), würdest Du C++ lieben. 

Bei mir hat's damals ähnlich angefangen :-)

oki,

Steffen

-- 
Dieses Schreiben wurde maschinell erstellt,
es trägt daher weder Unterschrift noch Siegel.



Mehr Informationen über die Mailingliste linux-l