Objektorientierung in C

Lesezeit: 14 Minuten

Was wäre eine Reihe raffinierter Präprozessor-Hacks (ANSI C89/ISO C90-kompatibel), die eine Art hässliche (aber brauchbare) Objektorientierung in C ermöglichen?

Ich bin mit ein paar verschiedenen objektorientierten Sprachen vertraut, antworten Sie also bitte nicht mit Antworten wie “Lernen Sie C++!”. Ich habe gelesen “Objektorientierte Programmierung mit ANSI C” (in acht nehmen: PDF-Format) und einige andere interessante Lösungen, aber ich interessiere mich hauptsächlich für Ihre :-)!


Siehe auch Können Sie objektorientierten Code in C schreiben?

  • Kann ich reagieren, um D zu lernen und das c-kompatible abi dort zu verwenden, wo Sie C wirklich brauchen? digitalmars.com/d

    – Tim Matthews

    6. Januar 2009 um 11:53 Uhr

  • @Dinah: Danke für das “Siehe auch”. Dieser Beitrag war interessant.

    anon

    6. Januar 2009 um 22:20 Uhr

  • Die interessante Frage scheint zu sein, warum Sie einen Präprozessor-Hack von OOP auf C wollen.

    – Calyth

    8. Januar 2009 um 19:13 Uhr

  • @Calyth: Ich finde, dass OOP nützlich ist und “ich arbeite mit einigen eingebetteten Systemen, die nur wirklich einen C-Compiler zur Verfügung haben” (von oben). Finden Sie außerdem raffinierte Präprozessor-Hacks nicht interessant anzusehen?

    anon

    9. Januar 2009 um 21:09 Uhr

  • Mögliches Duplikat von Können Sie objektorientierten Code in C schreiben?

    – Ciro Santilli OurBigBook.com

    29. April 2016 um 9:52 Uhr

Benutzeravatar von Adam Rosenfield
Adam Rosenfeld

Ich würde davon abraten, Präprozessoren zu verwenden, um zu versuchen, die C-Syntax eher der einer anderen, stärker objektorientierten Sprache anzugleichen. Auf der einfachsten Ebene verwenden Sie einfach einfache Strukturen als Objekte und geben sie mit Zeigern weiter:

struct monkey
{
    float age;
    bool is_male;
    int happiness;
};

void monkey_dance(struct monkey *monkey)
{
    /* do a little dance */
}

Um Dinge wie Vererbung und Polymorphismus zu erreichen, müssen Sie etwas härter arbeiten. Sie können manuelle Vererbung durchführen, indem Sie das erste Mitglied einer Struktur eine Instanz der Superklasse sein lassen, und dann können Sie Zeiger auf Basis- und abgeleitete Klassen frei umwandeln:

struct base
{
    /* base class members */
};

struct derived
{
    struct base super;
    /* derived class members */
};

struct derived d;
struct base *base_ptr = (struct base *)&d;  // upcast
struct derived *derived_ptr = (struct derived *)base_ptr;  // downcast

Um Polymorphie (d. h. virtuelle Funktionen) zu erhalten, verwenden Sie Funktionszeiger und optional Funktionszeigertabellen, die auch als virtuelle Tabellen oder vtables bekannt sind:

struct base;
struct base_vtable
{
    void (*dance)(struct base *);
    void (*jump)(struct base *, int how_high);
};

struct base
{
    struct base_vtable *vtable;
    /* base members */
};

void base_dance(struct base *b)
{
    b->vtable->dance(b);
}

void base_jump(struct base *b, int how_high)
{
    b->vtable->jump(b, how_high);
}

struct derived1
{
    struct base super;
    /* derived1 members */
};

void derived1_dance(struct derived1 *d)
{
    /* implementation of derived1's dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1's jump function */
}

/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
    &derived1_dance, /* you might get a warning here about incompatible pointer types */
    &derived1_jump   /* you can ignore it, or perform a cast to get rid of it */
};

void derived1_init(struct derived1 *d)
{
    d->super.vtable = &derived1_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

struct derived2
{
    struct base super;
    /* derived2 members */
};

void derived2_dance(struct derived2 *d)
{
    /* implementation of derived2's dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2's jump function */
}

struct base_vtable derived2_vtable =
{
   &derived2_dance,
   &derived2_jump
};

void derived2_init(struct derived2 *d)
{
    d->super.vtable = &derived2_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

int main(void)
{
    /* OK!  We're done with our declarations, now we can finally do some
       polymorphism in C */
    struct derived1 d1;
    derived1_init(&d1);

    struct derived2 d2;
    derived2_init(&d2);

    struct base *b1_ptr = (struct base *)&d1;
    struct base *b2_ptr = (struct base *)&d2;

    base_dance(b1_ptr);  /* calls derived1_dance */
    base_dance(b2_ptr);  /* calls derived2_dance */

    base_jump(b1_ptr, 42);  /* calls derived1_jump */
    base_jump(b2_ptr, 42);  /* calls derived2_jump */

    return 0;
}

Und so macht man Polymorphismus in C. Es ist nicht schön, aber es funktioniert. Es gibt einige schwierige Probleme bei Zeigerumwandlungen zwischen Basis- und abgeleiteten Klassen, die sicher sind, solange die Basisklasse das erste Mitglied der abgeleiteten Klasse ist. Mehrfachvererbung ist viel schwieriger – in diesem Fall müssen Sie, um zwischen anderen Basisklassen als der ersten zu wechseln, Ihre Zeiger basierend auf den richtigen Offsets manuell anpassen, was wirklich schwierig und fehleranfällig ist.

Eine weitere (knifflige) Sache, die Sie tun können, ist, den dynamischen Typ eines Objekts zur Laufzeit zu ändern! Sie weisen ihm einfach einen neuen vtable-Zeiger zu. Sie können sogar einige der virtuellen Funktionen selektiv ändern, während Sie andere beibehalten und so neue Hybridtypen erstellen. Achten Sie nur darauf, eine neue vtable zu erstellen, anstatt die globale vtable zu ändern, da Sie sonst versehentlich alle Objekte eines bestimmten Typs beeinflussen.

  • Adam, der Spaß am Ändern der globalen Vtable eines Typs besteht darin, das Ententippen in C zu simulieren. 🙂

    – Jmucchiello

    28. September 2009 um 14:59 Uhr

  • Jetzt tut mir C++ leid… Nun, natürlich ist die C++-Syntax klarer, aber da es keine triviale Syntax ist, werde ich gemildert. Ich frage mich, ob etwas Hybrides zwischen C++ und C erreicht werden könnte, also wäre void* immer noch ein gültiger castbarer Typ. Das Teil mit struct derived {struct base super;}; Es ist offensichtlich zu erraten, wie es funktioniert, da es nach der Byte-Reihenfolge richtig ist.

    – Jokon

    4. Januar 2011 um 15:41 Uhr

  • +1 für eleganten Code, gut geschrieben. Das ist genau das, wonach ich gesucht habe!

    – Homunculus Reticulli

    1. Mai 2012 um 7:09 Uhr

  • Gut erledigt. Genau so habe ich es gemacht und es ist auch der richtige Weg. Anstatt einen Zeiger auf die Struktur/das Objekt zu benötigen, sollten Sie einfach einen Zeiger auf eine Ganzzahl (Adresse) übergeben. Dies würde es Ihnen ermöglichen, jede Art von Objekt für unbegrenzte polymorphe Methodenaufrufe zu übergeben. Außerdem fehlt nur noch eine Funktion zum Initialisieren Ihrer Strukturen (Objekte/Klassen). Dies würde eine malloc-Funktion beinhalten und einen Zeiger zurückgeben. Vielleicht füge ich ein Stück hinzu, wie man die Nachrichtenübermittlung (objective-c) in C durchführt.

    Benutzer922475

    24. Juli 2012 um 19:37 Uhr

  • Dies ist der Strohhalm, der mich von C++ brach, und C mehr zu verwenden (bevor ich C++ nur für die Vererbung verwendet habe) Vielen Dank

    – Anne Quinn

    25. Mai 2013 um 4:05 Uhr


Benutzeravatar von Kieveli
Kieveli

Ich habe einmal mit einer C-Bibliothek gearbeitet, die auf eine Weise implementiert war, die mir ziemlich elegant vorkam. Sie hatten in C einen Weg geschrieben, Objekte zu definieren und dann von ihnen zu erben, sodass sie so erweiterbar wie ein C++-Objekt waren. Die Grundidee war folgende:

  • Jedes Objekt hatte seine eigene Datei
  • Öffentliche Funktionen und Variablen werden in der .h-Datei für ein Objekt definiert
  • Private Variablen und Funktionen befanden sich nur in der .c-Datei
  • Zum “Erben” wird eine neue Struktur erstellt, wobei das erste Mitglied der Struktur das Objekt ist, von dem geerbt werden soll

Erben ist schwer zu beschreiben, aber im Grunde war es folgendes:

struct vehicle {
   int power;
   int weight;
}

Dann in einer anderen Datei:

struct van {
   struct vehicle base;
   int cubic_size;
}

Dann könnten Sie einen Lieferwagen im Speicher erstellen und von Code verwenden, der nur Fahrzeuge kannte:

struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );

Es funktionierte wunderbar, und die .h-Dateien definierten genau, was Sie mit jedem Objekt tun sollten.

  • Ich mag diese Lösung sehr, außer dass alle Interna des “Objekts” öffentlich sind.

    – Lawrence Dol

    6. Januar 2009 um 5:18 Uhr

  • @Software Monkey: C hat keine Zugriffskontrolle. Die einzige Möglichkeit, Implementierungsdetails zu verbergen, besteht darin, über undurchsichtige Zeiger zu interagieren, was ziemlich schmerzhaft werden kann, da auf alle Felder über Zugriffsmethoden zugegriffen werden müsste, die wahrscheinlich nicht eingebettet werden können.

    – Adam Rosenfield

    6. Januar 2009 um 5:26 Uhr

  • @Adam: Compiler, die Link-Time-Optimierungen unterstützen, werden sie problemlos einbetten …

    – Christoph

    6. Januar 2009 um 13:01 Uhr

  • Wenn Sie dies tun, sollten Sie auch sicherstellen, dass alle Funktionen in der .c-Datei, die nicht als öffentlich definiert sind, als statisch definiert sind, damit sie nicht als benannte Funktionen in Ihren Objektdateien landen. Das stellt sicher, dass niemand ihre Namen in der Linkphase finden kann.

    – Jmucchiello

    28. September 2009 um 15:01 Uhr

  • @Marcel: C wurde verwendet, weil der Code auf Low-Level-Boards bereitgestellt wurde, auf denen eine Vielzahl von Prozessoren für autonome Systeme ausgeführt wurden. Sie alle unterstützten das Kompilieren von C in ihre jeweiligen nativen Binärdateien. Der Ansatz machte den Code sehr einfach zu lesen, sobald Sie erkannten, was sie zu tun versuchten.

    – Kieveli

    3. Juni 2013 um 12:49 Uhr

Benutzeravatar von Philant
Philant

C-Objektsystem (COS) klingt vielversprechend (es ist noch in der Alpha-Version). Aus Gründen der Einfachheit und Flexibilität versucht es, die verfügbaren Konzepte minimal zu halten: einheitliche objektorientierte Programmierung einschließlich offener Klassen, Metaklassen, Eigenschafts-Metaklassen, Generika, Multimethoden, Delegation, Besitz, Ausnahmen, Verträge und Schließungen. Da ist ein Entwurf; Schmierpapier (PDF), die es beschreibt.

Ausnahme in C ist eine C89-Implementierung von TRY-CATCH-FINALLY, die in anderen OO-Sprachen zu finden ist. Es kommt mit einer Testsuite und einigen Beispielen.

Beides von Laurent Deniau, an dem viel gearbeitet wird OOP in C.

  • @vonbrand COS wurde auf Github migriert, wo der letzte Commit im letzten Sommer stattfand. Reife kann mangelndes Commitment erklären.

    – Philant

    15. September 2017 um 18:55 Uhr

Benutzeravatar von James Cape
James Kap

Der GNOME-Desktop für Linux ist in objektorientiertem C geschrieben und hat ein Objektmodell namens “GObject“, das Eigenschaften, Vererbung, Polymorphismus sowie einige andere Extras wie Referenzen, Ereignisbehandlung (als “Signale” bezeichnet), Laufzeittypisierung, private Daten usw. unterstützt.

Es enthält Präprozessor-Hacks, um Dinge wie Typumwandlung in der Klassenhierarchie usw. zu erledigen. Hier ist eine Beispielklasse, die ich für GNOME geschrieben habe (Dinge wie gchar sind Typedefs):

Klasse Quelle

Klassenkopf

Innerhalb der GObject-Struktur gibt es eine GType-Ganzzahl, die als magische Zahl für das dynamische Typisierungssystem von GLib verwendet wird (Sie können die gesamte Struktur in einen “GType” umwandeln, um ihren Typ zu finden).

Benutzeravatar von zebrabox
Zebrabox

Etwas off-topic, aber der ursprüngliche C++-Compiler, Vorderseitekompilierte C++ nach C und dann nach Assembler.

Konserviert hier.

  • Ich habe es tatsächlich schon einmal gesehen. Ich glaube, es war ein schönes Stück Arbeit.

    anon

    5. August 2009 um 10:11 Uhr

  • @Anthony Cuozzo: Stan Lippman hat ein großartiges Buch mit dem Titel „C++ – Inside the object model“ geschrieben, in dem er viele seiner Erfahrungen und Designentscheidungen beim Schreiben und Pflegen von c-front erzählte. Es ist immer noch eine gute Lektüre und hat mir vor vielen Jahren beim Übergang von C zu C++ immens geholfen

    – Zebrabox

    5. August 2009 um 10:40 Uhr

Benutzeravatar von jjnguy
jjnguy

Wenn Sie sich Methoden vorstellen, die Objekte als statische Methoden aufrufen, die ein implizites ‘this‘ in die Funktion kann es das OO-Denken in C erleichtern.

Zum Beispiel:

String s = "hi";
System.out.println(s.length());

wird:

string s = "hi";
printf(length(s)); // pass in s, as an implicit this

Oder sowas ähnliches.

  • Ich habe es tatsächlich schon einmal gesehen. Ich glaube, es war ein schönes Stück Arbeit.

    anon

    5. August 2009 um 10:11 Uhr

  • @Anthony Cuozzo: Stan Lippman hat ein großartiges Buch mit dem Titel „C++ – Inside the object model“ geschrieben, in dem er viele seiner Erfahrungen und Designentscheidungen beim Schreiben und Pflegen von c-front erzählte. Es ist immer noch eine gute Lektüre und hat mir vor vielen Jahren beim Übergang von C zu C++ immens geholfen

    – Zebrabox

    5. August 2009 um 10:40 Uhr

Ich habe so etwas früher in C gemacht, bevor ich wusste, was OOP ist.

Es folgt ein Beispiel, das einen Datenpuffer implementiert, der bei Bedarf wächst, wenn eine minimale Größe, ein Inkrement und eine maximale Größe gegeben sind. Diese spezielle Implementierung basierte auf “Elementen”, das heißt, sie wurde entwickelt, um eine listenähnliche Sammlung eines beliebigen C-Typs zu ermöglichen, nicht nur einen Byte-Puffer mit variabler Länge.

Die Idee ist, dass das Objekt mit xxx_crt() instanziiert und mit xxx_dlt() gelöscht wird. Jede der “Member”-Methoden benötigt einen speziell typisierten Zeiger, um damit zu arbeiten.

Auf diese Weise habe ich eine verknüpfte Liste, einen zyklischen Puffer und eine Reihe anderer Dinge implementiert.

Ich muss gestehen, ich habe mir nie Gedanken darüber gemacht, wie man Vererbung mit diesem Ansatz umsetzt. Ich stelle mir vor, dass eine Mischung aus dem, was Kieveli anbietet, ein guter Weg sein könnte.

dtb.c:

#include <limits.h>
#include <string.h>
#include <stdlib.h>

static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl);

DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) {
    DTABUF          *dbp;

    if(!minsiz) { return NULL; }
    if(!incsiz)                  { incsiz=minsiz;        }
    if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz;        }
    if(minsiz+incsiz>maxsiz)     { incsiz=maxsiz-minsiz; }
    if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; }
    memset(dbp,0,sizeof(*dbp));
    dbp->min=minsiz;
    dbp->inc=incsiz;
    dbp->max=maxsiz;
    dbp->siz=minsiz;
    dbp->cur=0;
    if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; }
    return dbp;
    }

DTABUF *dtb_dlt(DTABUF *dbp) {
    if(dbp) {
        free(dbp->dta);
        free(dbp);
        }
    return NULL;
    }

vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); }
    if((dbp->cur + dtalen) > dbp->siz) {
        void        *newdta;
        vint        newsiz;

        if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; }
        else                                       { newsiz=dbp->cur+dtalen;   }
        if(newsiz>dbp->max) { errno=ETRUNC; return -1; }
        if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; }
        dbp->dta=newdta; dbp->siz=newsiz;
        }
    if(dtalen) {
        if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); }
        else       { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen);   }
        dbp->cur+=dtalen;
        }
    return 0;
    }

static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) {
    byte            *sp,*dp;

    for(sp=(byte*)src,dp=(byte*)dst; len; len--,sp++,dp++) { *dp=tbl[*sp]; }
    }

vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...) {
    byte            textÝ501¨;
    va_list         ap;
    vint            len;

    va_start(ap,format); len=sprintf_len(format,ap)-1; va_end(ap);
    if(len<0 || len>=sizeof(text)) { sprintf_safe(text,sizeof(text),"STRTOOLNG: %s",format); len=(int)strlen(text); }
    else                           { va_start(ap,format); vsprintf(text,format,ap); va_end(ap);                     }
    return dtb_adddta(dbp,xlt256,text,len);
    }

vint dtb_rmvdta(DTABUF *dbp,vint len) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(len > dbp->cur) { len=dbp->cur; }
    dbp->cur-=len;
    return 0;
    }

vint dtb_reset(DTABUF *dbp) {
    if(!dbp) { errno=EINVAL; return -1; }
    dbp->cur=0;
    if(dbp->siz > dbp->min) {
        byte *newdta;
        if((newdta=(byte*)realloc(dbp->dta,(vuns)dbp->min))==NULL) {
            free(dbp->dta); dbp->dta=null; dbp->siz=0;
            return -1;
            }
        dbp->dta=newdta; dbp->siz=dbp->min;
        }
    return 0;
    }

void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen) {
    if(!elmlen || (elmidx*elmlen)>=dbp->cur) { return NULL; }
    return ((byte*)dbp->dta+(elmidx*elmlen));
    }

dtb.h

typedef _Packed struct {
    vint            min;                /* initial size                       */
    vint            inc;                /* increment size                     */
    vint            max;                /* maximum size                       */
    vint            siz;                /* current size                       */
    vint            cur;                /* current data length                */
    void            *dta;               /* data pointer                       */
    } DTABUF;

#define dtb_dtaptr(mDBP)                (mDBP->dta)
#define dtb_dtalen(mDBP)                (mDBP->cur)

DTABUF              *dtb_crt(vint minsiz,vint incsiz,vint maxsiz);
DTABUF              *dtb_dlt(DTABUF *dbp);
vint                dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen);
vint                dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...);
vint                dtb_rmvdta(DTABUF *dbp,vint len);
vint                dtb_reset(DTABUF *dbp);
void                *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);

PS: vint war einfach eine Typdefinition von int – ich habe es verwendet, um mich daran zu erinnern, dass seine Länge von Plattform zu Plattform variabel war (für die Portierung).

  • Holy Moly, das könnte einen verschleierten C-Wettbewerb gewinnen! ich mag das! 🙂

    – Pferdemensch

    29. Juli 2009 um 19:58 Uhr

  • @horseyguy Nein, das konnte nicht. Es wurde veröffentlicht. Sie betrachten auch die Aufnahme von Header-Dateien als Missbrauch gegen das iocccsize-Tool. Es ist auch kein vollständiges Programm. 2009 hatte keinen Wettbewerb, daher kann ich die iocccsize nicht vergleichen. Die CPP wurde auch viele Male missbraucht, also ist sie ziemlich alt. usw. Entschuldigung. Ich versuche nicht, negativ zu sein, ich bin jedoch realistisch. Ich verstehe Ihre Bedeutung irgendwie und es ist eine gute Lektüre und ich habe es positiv bewertet. (Und ja, ich nehme daran teil und ja, ich gewinne auch.)

    – Pryftan

    24. Februar 2020 um 16:46 Uhr


1425110cookie-checkObjektorientierung in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy