Pseudo-Zugriffsrechte unter C

Autor: 'Kriz'

In diesem Tutorial geht es um eine Art Zugriffsregelung bei C Programmen. Während in C++ Zugriffsregelung innerhalb der Klassenstruktur möglich ist (public, protected und private), kennt C eine solche Zugriffsregelung nicht. Dort sind fast alle Variablen und Funktionen public, also öffentlich, deklariert.

Das Tutorial setzt auf dieses 'fast' des Satzes von oben. Es gibt einen Umweg in C, etwas ähnliches zu realisieren, wobei man hier aber nur die Holzhammermethode nehmen kann: Entweder public oder private. Ein protected dagegen gibt es nicht.

Zuerst einmal etwas graue Theorie:

In C/C++ unterscheidet man beim Zugriff auf Variablen oder Funktionen zwischen dem Gültigkeitsbereich und dem Sichtbarkeitsbereich. Der Gültigkeitsbereich erstreckt sich bei C ab dem Ort der Deklaration bis zum Ende der Deklaration. In den meisten Fällen ist das entweder der Anfang eines Blocks bis zu dessen Ende oder man spricht von einer globalen Gültigkeit, falls Variablen und Funktionen außerhalb jeglicher Funktionsblöcke deklariert werden.

Dagegen spricht man bei der Sichtbarkeit von dem Zugriffsverhalten auf eine Variable oder Funktion. So können beispielsweise Funktionen aus anderen C-Modulen oder Libraries ganz einfach dadurch sichtbar gemacht werden, indem man sie in jener Quelldatei per extern Schlüsselwort global deklariert, in der sie benötigt werden. Der Linker bindet dann durch das sogenannte „external linkage“ diese Fremd-Variablen/-Funktionen mit ihrer Quelle. Gerade in C ist diese Technik sehr verbreitet. Im folgenden ein kurzes Codebeispiel:

// externe_datei.c
 
int global_int = 5; // Hier wird global_int definiert
 
// datei.c
 
extern int global_int; // und hier wird's benötigt

Nun fragt man sich sicherlich, wieso man überhaupt extern benötigt, wenn man per #include einfach die passende Headerdatei samt Deklarationen einbinden lassen kann. Nun, das Einbinden einer Headerdatei ist ja im ersten Sinne nichts anderes als das nackte Einfügen von Quellcode. Erst danach geht der eigentliche Compiler durch den Quellcode und findet nun Deklarationen, deren Implementierungen aber nirgends im restlichen Quellcode aufzufinden sind. Dadurch folgert der Compiler, daß es sich hierbei um Variablen, Strukturen, Enumerationen und Funktionen ect. handelt, die woanders existieren. Der Linker führt daher nach einem entsprechenden Hinweis des Compilers eine Suche nach den Implementierungen. Die findet er dann in einer anderen Quelldatei bzw. in der daraus erzeugten Objektdatei. Das der Linker auch alles vorfindet, übernimmt in VisualC++ oder Dev-C++ beispielsweise die IDE selber, während manuell arbeitende Programmierer oftmals alle Angaben direkt in sogenannte Makefiles schreiben, die auf Abhängigkeiten basieren. Beispielsweise sieht ein typisches (simples) Makefile unter Linux so aus:

# Makefile XYZ (das ist ein Kommentar)
 
# Abhängigkeiten der Binary von den Objektdateien
testprogramm : main.o quelle1.o quelle2.o
    gcc -o testprogramm main.o quelle1.o quelle2.o
 
# Abhängigkeit von main.o
main.o : main.c
    gcc -c main.c
 
# Abhängigkeit von quelle1.o
quelle1.o : quelle1.c
    gcc -c quelle1.c
 
# Abhängigkeit von quelle2.o
quelle2.o : quelle2.c
    gcc -c quelle2.c

Dummerweise verlinkt der Linker nun alle vorgefundenen Variablen, Funktionen usw., was meistens nicht wünschenswert ist und zudem auch zu Codekollisionen führen kann, d.h. u.U. werden Variablen oder Funktionsdeklarationen mehrfach behandelt, trotz eventueller Anti-Mehrfachbindungs-Schutzmechanismen.

Das Credo der Stunde lautet daher: Trennung von Deklaration und Definition (Implementierung)! In den Headerdateien stehen daher ausschließlich nur Deklarationen wie enum, struct, union oder Funktionsprototypen. Variablen sollten aus den oben genannten Gründen nicht in Headerdateien auftauchen, da sie ansonsten mehrfach eingebunden werden könnten. In den Quelldateien dagegen findet die Implementierung Platz. So auch Variablen und Funktionsdefinitionen.

Und nun haben wir den Salat: Die Variablen werden in Quelldateien implementiert, die man aber nicht in andere Quelldateien per #include oder ähnlichem einbinden kann! Sie gehören also einem fremden Sichtbarkeitsbereich an. Um nun aber dennoch auf diese fremden Variablen usw. zugreifen zu können, benötigt man eben das Zauberwort extern.

Definiert man nun beispielsweise eine Variable in einer Quelldatei, dann ist sie automatisch global, d.h. andere Quelldateien können Sie via extern sichtbar machen. Aber auch dieses Verhalten ist manchmal unerwünscht. Beispielsweise gibt es globale Variablen, die nur für den Prozeß innerhalb der Quelldatei von Bedeutung sind und außerhalb der Quelldatei nicht sichtbar sein sollen. Will man dieses Erreichen, so muß man die Variable als static deklarieren. Nun kann sie nicht mehr per extern in andere Quelldateien überführt werden. Statisch-globale Variablen sind nur innerhalb ihrer Quelldatei sichtbar und gültig. Und genau hier setzen wir nun an!

Wir denken uns jetzt einfach, daß alle globalen Variablen und Funktionen public sind und bequem durch extern verlinkt werden können. Dagegen denken wir uns, daß alle statisch-globalen Variablen und Funktionen private sind und sich der Sichtbarkeitsmachung durch extern entziehen.

Voilá :-)

Das Codegerüst

Um eine sinnvolle Anwendung für die Pseudo-Zugriffsregelung zu haben, sollte man aber auch auf statisch-globale Variablen wenigstens einen lesenden Zugriff besitzen. Dies erreicht man durch das Bereitstellen einer Get-Funktion auf die statisch-globale Variable. Diese kann dann einfach durch das Einbinden der Headerdatei in den Quellcode bekannt gemacht und benutzt werden. Sehen wir uns ein kleines Beispiel dazu an:

Nehmen wir mal an, wir haben zwei int-Werte. Der eine soll public sein, der andere private. Dennoch soll der private-Wert lesbar gemacht werden. Dann müssten wir folgendes programmieren:

// pseudo.h
 
int getPrivate(void);
 
// pseudo.c
 
#include "pseudo.h"
 
int publicValue = 10;  // Öffentlicher, globaler Wert
static int privateValue = 20;  // Privater, statisch-globaler Wert
 
int getPrivate()  // Implementierung von getPrivate()
{
    return privateValue;
}

Nun noch die Ansicht aus der Ziel-Quelldatei:

// main.c
 
#include <stdio.h>
#include "pseudo.h"
 
extern int publicValue; // Sichtbarkeit auf publicValue erzeugen
 
int main()
{
    printf("Public = %d\n", publicValue); // Ausgabe ist 10
 
    publicValue = 500;
 
    printf("Public = %d\n", publicValue); // Ausgabe ist 500
 
    printf("Private = %d\n", getPrivate()); // Ausgabe ist 20
}

Andere Späße wie extern int privateValue; oder privateValue = 2000; usw. sind nicht erlaubt und führen zu Fehlermeldungen, da privateValue weder sichtbar noch zugreifbar ist. Damit hätten wir eine kleine Pseudo-Zugriffsregelung in C erschaffen.

Tipps

Ähnlich wie in C++ kann man nun gezielt gewisse Aktionen auf statisch-globale Variablen zulassen, beispielsweise so etwas wie…

int getPrivate();
 
void setPrivate(int value);
 
void resetPrivate();
 
...

Anmerkung

Dieses Tutorial stammt aus der ehemaligen Sammlung des resourcecode.de und konnte dank der freundlichen Zustimmung des Autors in das thewall-Wiki übertragen werden.

Die Verwendung aller Dokumente einschließlich der Abbildungen ausschließlich zu nichtkommerziellen Zwecken. Verbreitung des Dokuments auf Speichermedien, (insbesondere auf CD-ROMs als Beilage zu Zeitschriften und Magazinen oder sog. "Mission-Packs" etc.) ist untersagt.
 
coding/cpp/pseudo_zugriffsrechte_c.txt · Zuletzt geändert: 2008/10/30 13:28 von TomMe