Forum: Compiler & IDEs Funktionszeiger im struct in C


von NoIdea (Gast)


Lesenswert?

Hallo,

ich habe ein Verständnisproblem mit Funktionszeigern evtl. hackt es auch 
wo anders (bin in C nicht wirklich Sattelfest). Da ich modular 
programmieren und bereits programmierte Teile wiederverwenden möchte, 
hab ich mir folgendes überlegt: Es wäre doch toll, wenn ich einen 
Datentyp für eine Bsp. UART erstelle, der die zugehörigen Variablen und 
Funktionen mitbringt (ähnlich wie Klassen in der objektorientierten 
Programmierung).

Beispiel:
1
typedef void (*VoidFnct)( char [] );
2
3
struct
4
{
5
   char buffer[10] = "         ";
6
   VoidFnct send = uart_send;
7
}uart;
8
9
void uart_send(char [] to_send)
10
{
11
    // TODO senderoutine
12
}
13
14
int main ()
15
{
16
    uart.send("hallo");
17
}

Wie ihr seht möchte ich, dass über die Struktur auf die Funktionen 
zugegriffen wird. Wichtig ist auch, dass nicht erst der Funktionspointer 
initialisiert werden muss, nachdem eine Instanz der Struktur angelegt 
wurde. Mein Problem ist, wie mach ich das dem Compiler klar?

Ich hoffe ihr versteht was ich meine, sonst muss ich versuchen mein 
Problem anders zu schildern.

Grüzi, Willi

von sous (Gast)


Lesenswert?

Hallo Willi,
Du schreibst: "... der die zugehörigen Variablen und
Funktionen mitbringt (ähnlich wie Klassen in der objektorientierten
Programmierung)."

warum programmierst Du es denn nicht einfach objektorientiert in C++ 
statt in C?

von NoIdea (Gast)


Lesenswert?

Darüber habe ich auch nachgedacht. Ich habe da noch ein Posting im Kopf, 
in dem es hiess dass ein grosser Overhead erzeugt wird und für kleine 
8-Bitter nicht geeignet wäre. Wenn es stimmen sollte, fiele c++ dann aus 
dem Rennen.

Willi

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Wenn du die Instanz wie oben anlegst, wird die Funktionsadresse vom 
Compiler zur Compilezeit in die Struktur eingetragen. Wo genau ist das 
Problem?

von Sven P. (Gast)


Lesenswert?

1
struct
2
{
3
   char buffer[10] = "         ";
4
   VoidFnct send = uart_send;
5
}uart;

Das Prinzip ist zwar recht weit verbreitet und macht auch oft Sinn. Aber 
über denselben hast du dich ja schon gefragt, als du überlegt hast, mit 
C++ zu arbeiten.
Wenn sich diese Funktionen später wirklich mal ändern, ist der Ansatz in 
Ordnung. Ansonsten würd ich aber dazu tendieren, in der Struktur nur 
Daten aufzubewahren und dann halt einen Satz von Funktionen zu 
erstellen, ohne diese auch per Zeiger in der Struktur zu verewigen. Das 
braucht nur unnötig Speicher in dem Fall.

von NoIdea (Gast)


Lesenswert?

@Stefan:

Hast recht so funktioniert es auch. Fängt man jedoch an alles in 
verschiedene Files auszulagern, so fangen die Probleme an.

Beispiel:

uart.c
1
#include "uart.h"
2
3
struct
4
{
5
   char buffer[10] = "         ";
6
   VoidFnct send = uart_send;
7
}uart;
8
9
void uart_send(char [] to_send)
10
{
11
    // TODO senderoutine
12
}

uart.h
1
#ifndef UART_H
2
#define UART_H
3
4
#include "common.h"
5
6
typedef void (*VoidFnct)( char [] );
7
8
#endif

main.c
1
#include "common.h"
2
3
int main ()
4
{
5
  uart.send("hello");
6
  while(1);
7
}

common.h
1
#ifndef COMMON_H
2
#define COMMON_H
3
4
#include <avr/io.h>
5
6
#endif

Versuch zu kompilieren (WinAVR mit AVR Studio) mit Fehlermeldung:
../main.c:5: error: 'uart' undeclared (first use in this function)

Wie bringe ich jetzt dem Compiler bei, dass uart existiert?

Wie erwähnt möchte ich eine Pointerinitialisierung, wenn eine Instanz 
der Struktur angelegt wurde, vermeiden.

Willi

von holzmagnet (Gast)


Lesenswert?

hi
1
#include "uart.h"
2
3
extern struct
4
{
5
   char buffer[10] = "         ";
6
   VoidFnct send = uart_send;
7
}uart;
8
9
void uart_send(char [] to_send)
10
{
11
    // TODO senderoutine
12
}

das könnte evtl. weiterhelfen. Mit extern machst du die Sache 
öffentlich. Kanns aber leider net testen. Probiers ma aus.

cya

von Karl H. (kbuchegg)


Lesenswert?

holzmagnet wrote:
> hi
>
1
> #include "uart.h"
2
> 
3
> extern struct
4
> {
5
>    char buffer[10] = "         ";
6
>    VoidFnct send = uart_send;
7
> }uart;
8
> 
9
> void uart_send(char [] to_send)
10
> {
11
>     // TODO senderoutine
12
> }
13
>
>
> das könnte evtl. weiterhelfen. Mit extern machst du die Sache
> öffentlich. Kanns aber leider net testen. Probiers ma aus.

Das dürfte eigentlich nicht funktionieren. In dem Moment
in dem da eine Initialisierung ist, ist das Ganze eine Definition
und keine Deklaration mehr (das extern wird in dem Fall quasi
ignoriert). Das einzige was einen dann noch rettet, ist das
Ignorieren der 'One Definition Rule' im gcc-Linker.

Im Übrigen schliesse ich mich sous bzw. Sven an.
Es macht nicht wirklich Sinn, objektorientierte Konzepte aus
C++ 1:1 nach C übernehmen zu wollen. In C hast du nun mal das
Gegenstück zu einem Konstruktor nicht. Eine Initialisierung ist
da nur ein müder Ersatz dafür.

> Ich habe da noch ein Posting im Kopf,
> in dem es hiess dass ein grosser Overhead erzeugt wird

Nicht wirklich. Kommt immer darauf an, was man aus C++ benutzt.
Letztendlich implementiert ein C++ Compiler das von dir gewünschte
Verhalten (virtuelle Funktionen) auch nicht anders, nur macht er
es ein bischen cleverer, indem nicht jedes Objekt eine Tabelle
von Funktionszeigern beeinhaltet, sondern dafür eine eigene
Struktur abgestellt wird und im Objekt ein Pointer auf eben diese
Funktionspointertabelle abgelegt wird.

Die vernünftigste Lösung ist immer noch ein Mittelweg und die
Forderung nach Initialisierung der Funktionspointer über Bord
zu schmeissen. Da macht man sich dann eine Init Funktion, in der
die Funktionspointer rein kommen und die für jedes Objekt auf-
gerufen werden muss.

*Wenn man denn unbedingt 'objektorientiert' in C arbeiten will*
*und die object.function Notation haben möchte.*

Ob das so Sinn macht steht auf einem anderen Blatt. Speichertechnisch
ist das ein ziemlicher Horror, solange die Funktionspointer nicht
ihre wirkliche Stärke zur Implementierung von virtuellen Funktionen
ausspielen können.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

> Im Übrigen schliesse ich mich sous bzw. Sven an.

Dito. Aber als Schnellschuss bzw. der Neugier wegen, würde ich mal 
folgenden Aufbau testen:

uart.c
======

#include "uart.h"

suart uart = { "         ", uart_send };

void uart_send(char [] to_send)
{
    // TODO senderoutine
}



uart.h
======

#ifndef UART_H
#define UART_H

#include "common.h"

typedef void (*VoidFnct)( char [] );

typedef struct
{
   char buffer[10];
   VoidFnct send;
} suart;

extern suart uart; // in uart.c

#endif



main.c
======

#include "common.h"
#include "uart.h"

int main (void)
{
  uart.send("hello");
  while(1);
}



common.h
========

#ifndef COMMON_H
#define COMMON_H

#include <avr/io.h>

#endif

von NoIdea (Gast)


Lesenswert?

Karl heinz Buchegger schreibt:
"Speichertechnisch ist das ein ziemlicher Horror, solange die 
Funktionspointer nicht ihre wirkliche Stärke zur Implementierung von 
virtuellen Funktionen
ausspielen können."

Aha, ok das ist ein starkes Gegenargument. Werd wohl der Empfehlung 
folgen und  die Funktionen aus dem struct rauslassen, denn das umbiegen 
der Fkt.-pointer ist in den allermeisten Fällen nicht wirklich von 
Nöten. War nur ne Idee (keine gute aber immerhin ;) ).

Vielen Dank!!!

von Karl H. (kbuchegg)


Lesenswert?

NoIdea wrote:

> der Fkt.-pointer ist in den allermeisten Fällen nicht wirklich von
> Nöten. War nur ne Idee (keine gute aber immerhin ;) ).

Ne. ist doch ok.

Wenn die Memberfunktionen keine virtuellen Funktionen sind,
dann verändert ein C++ Compiler intern (*) einen Aufruf

   object.function( argument );

zu

  function( object, argument );

macht also auch einen stink normalen Funktionsaufruf daraus, bei
dem als erstes Argument (oder letztes, je nach Compiler) das
Objekt selbst übergeben wird (als Referenz).

Diese . Notation ist also im Grunde für einen C++ Compiler nur
'syntactic sugar' um eine andere Syntax für einen Member-
Funktionsaufruf zu haben, der ein bischen schöner aussieht und
Aufrufe von Memberfunktionen von Aufrufen von standalone
Funktionen zu unterscheiden.

(*) Das ist ihm vom C++ Standard natürlich nicht so vorgeschrieben,
aber seit Stroustroups Zeiten wird das so gemacht. Daher auch
unter anderem das viel gepriesene und noch viel öfter verfluchte
Name-Mangling von Member-Funktionsnamen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.