[C] Functies in een struct

Pagina: 1
Acties:
  • 141 views sinds 30-01-2008
  • Reageer

Acties:
  • 0 Henk 'm!

  • liquid_ice
  • Registratie: Februari 2001
  • Laatst online: 18-07 07:48
Eigelijk is m'n vraag best kort...

Kan je functies in een struct krijgen in de programmeertaal C ??

Zal ff wat uitleggen.
Ik ben aan het programmeren, maar dit moet in C gebeuren.
Het liefst zou ik m'n programma OO opzetten, maar dat kan dus niet.

Nou weet ik dat je in C het volgende WEL kan doen

code:
1
2
3
4
typedef struct  {
    int first;
    int last;
  }buffer;


en dan iets als
code:
1
2
buffer InkomendeBuffer;
InkomendeBuffer.first = 1;


Maar kan ik geen functies erin krijgen?
dat ik het dan kan aanroepen met iets als
code:
1
2
buffer InkomendeBuffer;
InkomendeBuffer.sorteer();

Klus page: http://klusthuis.blogspot.com


Acties:
  • 0 Henk 'm!

Anoniem: 47102

Ja dat kan, dat is wat met objecten noemt. C++ ondersteunt dat wel, C niet (officieel).
Als je perse in C wilt werken dan kan je een pointer naar een functie maken en die aanroepen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
struct bla{
void(* hoi)();
} blaat;

void functie(){
...
}

blaat BlaatInst;

BlaatInst.hoi = functie;

BlaatInst->hoi();

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

Nee kan niet. Maar je kunt natuurlijk wel gewoon OO programmeren door allemaal functies te maken die horen bij de buffer en die dus ook een pointer-naar-buffer als parameter verwachten

C:
1
2
3
4
5
6
7
8
typedef struct  {
    int first;
    int last;
  }buffer;

void buffer_sorteer(buffer * pBuffer);
void buffer_copy(buffer * pBufferTo, buffer * pBufferFrom);
/* etc */


@RoadRunner84: dat zorgt alleen maar voor onnodig veel memoryverbruik en mogelijke problemen met initialisatie :)

[ Voor 13% gewijzigd door .oisyn op 23-03-2007 13:08 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • igmar
  • Registratie: April 2000
  • Laatst online: 16-06 09:56

igmar

ISO20022

.oisyn schreef op vrijdag 23 maart 2007 @ 13:07:
C:
1
2
3
4
5
6
7
8
typedef struct  {
    int first;
    int last;
  }buffer;

void buffer_sorteer(buffer * pBuffer);
void buffer_copy(buffer * pBufferTo, buffer * pBufferFrom);
/* etc */
Waardoor je vooral in wat grotere applicaties extreem veel globals krijgt, en da's ook niet iets wat je wil.
@RoadRunner84: dat zorgt alleen maar voor onnodig veel memoryverbruik en mogelijke problemen met initialisatie :)
Hoezo ? Het is 1 pointer extra, en je kan alle functies waar je naar wijst vaak static maken, je hebt dan alleen een functie nodig die de betreffende struct initialiseert.
Het is een veel gebruikte methode, met vind ik, alleen maar voordelen.

Acties:
  • 0 Henk 'm!

  • cspare
  • Registratie: Oktober 2006
  • Laatst online: 19-06 06:56

cspare

What the deuce?!

igmar schreef op vrijdag 23 maart 2007 @ 15:10:
[...]


Waardoor je vooral in wat grotere applicaties extreem veel globals krijgt, en da's ook niet iets wat je wil.


[...]
Waarin verschilt het gebruik van een struct in het aantal globals die je nodig hebt? Als je via de pointer uit de struct een niet-global methode aanroept is het in feite toch ook gewoon global (alleen met een tussenstap extra die geen controles uit kan voeren)?

Offtopic: Is het zo dat als je een functie declaratie niet in een .h file opneemt dat hij als hetware private is? (dus niet aan te roepen vanuit een andere c-file?)

[ Voor 15% gewijzigd door cspare op 23-03-2007 15:21 ]

The one who says it cannot be done, should never interrupt the one who is doing it.


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 18-07 09:02
cspare schreef op vrijdag 23 maart 2007 @ 15:17:
Offtopic: Is het zo dat als je een functie declaratie niet in een .h file opneemt dat hij als hetware private is? (dus niet aan te roepen vanuit een andere c-file?)
Indien niet anders gespecificeerd heeft een functie external linkage. WIl je dat niet dan moet je em expliciet static maken.

Meeste C compilers zullen standaard echter klagen dat je geen functie declaratie kunnen vinden als je em aanroept zonder dat deze gedeclareerd staat in dezelfde compilatie unit

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • igmar
  • Registratie: April 2000
  • Laatst online: 16-06 09:56

igmar

ISO20022

cspare schreef op vrijdag 23 maart 2007 @ 15:17:
Waarin verschilt het gebruik van een struct in het aantal globals die je nodig hebt? Als je via de pointer uit de struct een niet-global methode aanroept is het in feite toch ook gewoon global (alleen met een tussenstap extra die geen controles uit kan voeren)?
Omdat je met de pointer-in-struct manier de functie static kan maken. Je moet dan alleen 1 globale functie hebben die de struct initialiseert.
Offtopic: Is het zo dat als je een functie declaratie niet in een .h file opneemt dat hij als hetware private is? (dus niet aan te roepen vanuit een andere c-file?)
Aan te roepen wel, alleen zal de compiler waarschijnlijk default return waarden gebruiken. De functie is echter nog steeds wel een global.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

igmar schreef op vrijdag 23 maart 2007 @ 15:10:

Waardoor je vooral in wat grotere applicaties extreem veel globals krijgt, en da's ook niet iets wat je wil.
Waarom wil je dat niet? Ik hoop dat je begrijpt dat een globale variabele iets anders is dan een globale functie. En dat namespace pollution ook geen issue is als je je functies gewoon goede namen geeft (dus prefixen met buffer als het gaat om buffer).
Hoezo ? Het is 1 pointer extra
1 pointer per functie per object ja. Terwijl het niet nodig is. En het komt de performance ook nog eens niet ten goede omdat de CPU niet van tevoren weet waar hij heenspringt (en dus zit je met hele pipeline flushes voordat ie de call kan doen). En voor polymorphisme kun je op z'n minst een vtable gebruiken, dan blijft het iig bij 1 pointer per object. En dan hoef je ook alleen nog maar de vtable in het object te laten wijzen naar een vooraf-geinitialiseerde vaste vtable (kun je meteen types vergelijken)

Bovendien ben je er met alleen een functiepointer nog niet, want je moet ook nog de thispointer meegeven. Dat wordt dus myBuffer->sorteer(myBuffer), beetje dubbelop :)

[ Voor 6% gewijzigd door .oisyn op 23-03-2007 16:07 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 00:56
Object-georienteerd programmeren in C komt best veel voor; er zijn eigenlijk drie manieren waarop je het kunt aanpakken.

1. Het simpelste idioom is wat .oisyn voorstelt: je definieert een structure met je data members, en een aantal functies die daarop werken. Eventueel kun je de inhoud van je structure helemaal ondoorzichtig maken, door een aparte functie te maken die 'm alloceert.
C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// header file:
struct Foo;
struct Foo *Foo_create();
struct void Foo_destroy();
int Foo_method(struct Foo *this);

// source:
struct Foo {
    int data;
};

struct Foo *Foo_create() {
   struct Foo *this = malloc(sizeof(struct Foo));
   this->data = 0;
   return this;
}

int Foo_method(struct Foo *this) {
    return this->data;
}

// etc.

Dit model wordt gebruikt door een heleboel C libraries. Voordeel is dat je de structure niet eens in je header file hoeft te declareren, en je dus een stricte scheiding hebt tussen je interface en je implementatie. Als Foo onderdeel van een shared library is, kun je de inhoud van je struct prima wijzigen (door extra velden toe te voegen bijvoorbeeld) zonder programma's die tegen je library linken kapot gaan.

Een belangrijk nadeel is dat je op deze manier niet makkelijk dingen als class inheritance of polymorphie kunt implementeren. Het is dus een heel beperkte vorm van OOP.

2. Een variant is wat RoadRunner84 voorstelt: de methoden die op een object werken zijn onderdeel van de struct. In hetzelfde voorbeeld als hierboven:
C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Foo {
    int data;
    int (method*) (struct Foo* this);
};

struct Foo *Foo_construct() { 
   struct Foo *this = malloc(sizeof(struct Foo)); 
   this->data = 0; 
   this->method = &Foo_method;
   return this; 
} 

// gebruik:
{
    struct Foo *foo = Foo_construct();
    return foo->method(foo);
}

Echt elegant is de C code niet; je moet nog steeds de instantie ook als argument aan z'n methode meegeven. Voordeel is wel dat je dingen als inheritance en polymorfie kunt implementeren (polymorfie is triviaal), maar een nadeel is dat je struct wel in de headers gedeclareert moet worden en dat als je wijzigingen maakt in je struct, je een incompatible library krijgt.

Sleepycat's BerkeleyDB (tegenwoordig van Oracle) gebruikt precies deze aanpak, en daarom zijn verschillende versies ook incompatible (en zie je op veel systemen veel verschillende versies van BerkeleyDB naast elkaar geïnstalleerd).

Het is dus niet zo vergezocht als .oisyn zegt; het is een prima methode voor relatief heavy-weight objecten, en als je niet van plan bent je ABI al te vaak te veranderen.

3. Een derde mogelijkheid is om objecten min of meer te implementeren zoals dat in C++ gedaan wordt. Dat wil zeggen, dat elke instantie een pointer heeft naar een beschrijving van de klasse, waarin ook de methoden van die klasse beschreven worden (in C++ is dat een vtable pointer).

Dit geeft je in principe volledige OOP mogelijkheden in C, met als nadeel dat de C compiler een heleboel dingen niet voor je doet, zoals bijvoorbeeld impliciete upcasts toestaan. Het komt er op neer dat je dan als programmeur zelf een heleboel conventies moet aanhouden om de boel correct en overzichtelijk te houden.

Deze aanpak wordt in GLib gebruikt voor OOP, en wordt voor een heleboel object-georienteerde C applicaties gebruikt (denk aan dingen als GTK, GStreamer, etc.) Als je zoiets wil implementeren, zou ik je aanraden om gewoon direct GLib te gebruiken, want dat scheelt ten eerste een heleboel werk, en ten tweede zorgt het ervoor dat je een min-of-meer standaard coding conventie gebruikt in plaats van je eigen, nieuwe conventie te introduceren. GLib gebruikt ook veel macro's om een heleboel implementatiedetails voor de programmeur te verbergen.

Voor details van GLib verwijs ik je graag door naar de GLib Reference Manual; ook erg interessant om door te lezen als je geen GLib gebruikt maar eens wil zien hoe je een goed OO-systeem voor C ontwerpt/implementeert.

Acties:
  • 0 Henk 'm!

  • igmar
  • Registratie: April 2000
  • Laatst online: 16-06 09:56

igmar

ISO20022

.oisyn schreef op vrijdag 23 maart 2007 @ 16:05:
Waarom wil je dat niet? Ik hoop dat je begrijpt dat een globale variabele iets anders is dan een globale functie. En dat namespace pollution ook geen issue is als je je functies gewoon goede namen geeft (dus prefixen met buffer als het gaat om buffer).
Omdat het niet nodig is. Een static variabele voorkomt namespace polution, en zorgt er als je het mij vraagt voor dat je code overzichtelijker word. Een grote function table in een shared lib is zowiezo geen goed idee : Dat levert je runtime linker een hoop extra werk op. Da's precies de reden waarom men prelinking heeft bedacht.
1 pointer per functie per object ja. Terwijl het niet nodig is. En het komt de performance ook nog eens niet ten goede omdat de CPU niet van tevoren weet waar hij heenspringt (en dus zit je met hele pipeline flushes voordat ie de call kan doen). En voor polymorphisme kun je op z'n minst een vtable gebruiken, dan blijft het iig bij 1 pointer per object.
Als je de struct meteen vult met data weet iig GCC prima wat ie er mee moet doen, en maakt er dan een soort tabel van :

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        .data
b:
        .long   1
        .long   c
        .long   d
        .long   e

        .text
        pushl b
        call *b+4
        movl    %eax, %edx
        movl    b, %eax
        movl    %eax, (%esp)
        leal    (%eax,%edx), %ebx
        call    *b+8
        addl    %eax, %ebx
        popl    %eax
        pushl   b
        call    *b+12


Goed, het zijn indirecte calls, maar dat zal vermoedelijk in C++ niet veel anders zijn, aangezien je ook dan van te voren niet weet waar je heen springt, aangezien de plaats van de objecten eveneens niet vaststaat.
En dan hoef je ook alleen nog maar de vtable in het object te laten wijzen naar een vooraf-geinitialiseerde vaste vtable (kun je meteen types vergelijken)
Dat kun je in C ook. De meeste compilers snappen het prima indien je de struct ook meteen initialiseert.
Bovendien ben je er met alleen een functiepointer nog niet, want je moet ook nog de thispointer meegeven. Dat wordt dus myBuffer->sorteer(myBuffer), beetje dubbelop :)
Klopt. Die code is alleen lelijk, maar ik denk niet dat dit erg veel zal uitmaken. Hij pushed het adres van b op de stack, en da's alles. Met de juiste gcc opties maakt ie er een register argument van. Met C++ spelen dit soort dingen net zo, en met de rest van OO talen zal het denk ik niet veel anders zijn.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

igmar schreef op vrijdag 23 maart 2007 @ 17:41:
Een grote function table in een shared lib is zowiezo geen goed idee : Dat levert je runtime linker een hoop extra werk op.
Ah ja, een linux iets. Daar had ik even niet aan gedacht, voor dll's moet je namelijk gewoon specificeren welke functie je exporteert.
Als je de struct meteen vult met data weet iig GCC prima wat ie er mee moet doen, en maakt er dan een soort tabel van :
Lokaal in een functie wel ja, als je een functie hebt die een object accepteert en daar een "memberfunctie" op aanroept, dan heeft de compiler geen idee waar die pointer nou naar wijst. In C++ heb je hier alleen last van als de functies virtual zijn, niet virtual memberfuncties hebben gewoon een vast adres. Non-virtual functies zijn dan ook meer zoals mijn suggestie, waar virtual functies meer zoals jouw suggestie werken (behalve dan dat er doorgaans een vtable tussen zit om het geheugenverbruik te beperken).
Dat kun je in C ook. De meeste compilers snappen het prima indien je de struct ook meteen initialiseert.
Euh ja, dat is ook precies wat ik zei. Als je functiepointers hanteert gebruik dan een vtable. In C dus :)

[ Voor 8% gewijzigd door .oisyn op 23-03-2007 18:17 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 18-06 11:36
igmar schreef op vrijdag 23 maart 2007 @ 17:41:
[...]
Goed, het zijn indirecte calls, maar dat zal vermoedelijk in C++ niet veel anders zijn, aangezien je ook dan van te voren niet weet waar je heen springt, aangezien de plaats van de objecten eveneens niet vaststaat.
De "plaats" van je objecten heeft niets te maken met je jump address. Het eerste is waar de data staat, en het tweede hangt alleen af van waar de code staat. Alle objecten van type FOO hebben eigen data maar delen de FOO code.

In C++ heb je dus zelfs een goede kans dat er niets eens een directe jump staat, als je compiler de desbetreffende functie inlined. Daarna kan die compiler ook nog register optimalisaties doen etc. dus de prijs van een indirecte call is behoorlijk hoog.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • 0 Henk 'm!

  • Robtimus
  • Registratie: November 2002
  • Laatst online: 18-07 11:07

Robtimus

me Robtimus no like you

Functies in structs zou je IMHO alleen moeten toepassen als de functie kan verschillen per struct. Een goed voorbeeld is callbacks in GLibs XML parsing. Elk XML parsing struct kan dan een verschillende callback hebben voor het afhandelen.

More than meets the eye
There is no I in TEAM... but there is ME
system specs


Acties:
  • 0 Henk 'm!

  • igmar
  • Registratie: April 2000
  • Laatst online: 16-06 09:56

igmar

ISO20022

.oisyn schreef op vrijdag 23 maart 2007 @ 18:16:
Ah ja, een linux iets. Daar had ik even niet aan gedacht, voor dll's moet je namelijk gewoon specificeren welke functie je exporteert.
't is meer een ELF / linker iets. Ik ken eigenlijk geen ELF platform wat het anders doet standaard. Met een linkerscript kun je wel dingen aanpassen overigens.
Lokaal in een functie wel ja, als je een functie hebt die een object accepteert en daar een "memberfunctie" op aanroept, dan heeft de compiler geen idee waar die pointer nou naar wijst.
Daar kun je inderdaad wel eens gelijk in hebben ja. Ik zal eens kijken of ik gcc 4.x aan de praat krijg, die schrijnt toch wat intelligenter te zijn.
In C++ heb je hier alleen last van als de functies virtual zijn, niet virtual memberfuncties hebben gewoon een vast adres. Non-virtual functies zijn dan ook meer zoals mijn suggestie, waar virtual functies meer zoals jouw suggestie werken (behalve dan dat er doorgaans een vtable tussen zit om het geheugenverbruik te beperken).
Mijn C++ is erg roestig, maar heb je in C++ niet precies hetzelfde probleem ? Je hebt pointer X die naar object Y wijst, en vanuit een functie is een memberfunctie van Y ook een offset ergens vanaf het beginadres van dat object lijkt me.
Euh ja, dat is ook precies wat ik zei. Als je functiepointers hanteert gebruik dan een vtable. In C dus :)
Ah, ok :-)

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

igmar schreef op maandag 26 maart 2007 @ 17:54:
Mijn C++ is erg roestig, maar heb je in C++ niet precies hetzelfde probleem ? Je hebt pointer X die naar object Y wijst, en vanuit een functie is een memberfunctie van Y ook een offset ergens vanaf het beginadres van dat object lijkt me.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct MyClass
{
    int i;

    void foo();
    virtual void bar();
};

int main()
{
    MyClass myObject;
    myObject.i = 3;
    myObject.foo();
    myObject.bar();
}

Wordt ruwweg gecompileerd als:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct MyClassVTable;

struct MyClass
{
    MyClassVTable * vtable;
    int i;
};

void MyClass_foo(MyClass* this);
void MyClass_bar(MyClass* this);

struct MyClassVTable
{
    void (*bar)(MyClass* this);
};

static MyClassVTable sMyClassVTable = { MyClass_bar };

MyClass_constructor(MyClass* this)
{
    this->vtable = &sMyClassVTable;
}


int main()
{
    MyClass myObject;
    MyClass_constructor(&myObject);
    myObject.i = 3;
    MyClass_foo(&myObject);
    myObject.vtable->bar(&myObject);
}


foo is niet virtual en is dus gewoon een vaste functie. Hij is niet polymorph en dus is het ook niet nodig om een pointer op te slaan - een aanroep van foo() op een MyClass zal altijd MyClass::foo() aanroepen. Dit is anders bij bar(), je zou een derived class kunnen maken met een eigen implementatie van bar(). Als je zo'n object cast naar z'n baseclass en dan bar() aanroept, dan zal de derived versie van bar() aangeroepen worden ipv MyClass::bar(). Daar is dus wél een pointer voor nodig.

Overigens, een degelijke compiler zal bovestaande aanroep naar bar() compileren als MyClass_bar(&myObject), omdat de compiler weet dat het een MyClass is en geen derived class - de benodige bar functie staat dus ook al vast.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1