Im Zuge des Interviews der letzten Ausgabe gelang es uns Carsten Jahn, einen der Programmierer des Blankerprogrammes Madhouse, für einen kleinen Exkurs in die Tiefen der Programmiersprache C zu gewinnen.
Ob er sich für weitere Themenschwerpunkte Zeit nehmen wird, richtet sich nicht zuletzt auch nach den Reaktionen.
In diesem Kurs wollen wir in einem Zwiegespräch C für Anfänger vermitteln. Als Ziel soll ein Programm erstellt werden, das eingegebene Texte zentrieren kann. Wir setzen Grundkenntnisse voraus.
Ziel des Kursteils ist es, C nicht systematisch zu erklären, sondern einen bestimmten Aspekt zu beleuchten. Man sollte sich also schon mal mit C beschäftigt haben. Trotzdem richtet sich das Thema an Anfänger.
Die Verwendung von Strings (auch Zeichenketten genannt) in C unterscheidet sich von der Verwendung in anderen Programmiersprachen darin, daß man in C genau über die Interna und Zusammenhänge Bescheid wissen muß, sonst kommt es zu schwer verständlichen Fehlern im Programm.
| Frage: | Womit beginnen wir? |
|---|---|
| Antwort: | Vielleicht sollten wir uns zuerst über das Grundkonzept von Strings in C klar werden. Es gibt nämlich keins! Stattdessen werden Strings in normalen Arrays (Feldern) abgelegt, Zeichen für Zeichen. Begonnen wird, wie immer bei C-Arrays, beim nullten Index, das Ende des Strings wird mit einem 0-Byte signalisiert. |
| Frage: | Was ist denn ein Null-Byte? |
| Antwort: | Weil Computer nur mit Zahlen umgehen können, hat man jedem Buchstaben eine Nummer zugewiesen (ASCII-Codierung). Zum Beispiel hat 'A' die Nummer 65. Mit den Nummern hat man aber selten zu tun, weil der Compiler die Umwandlung automatisch vornimmt. "0-Byte" bedeutet einfach nur, daß die Nummer 0 in das Array eingetragen wird. |
| Frage: | Was passiert, wenn man das 0-Byte wegläßt? |
| Antwort: | Versucht man beispielsweise, einen solchen String mit printf() (s.u.) auszugegeben, tritt die folgende Situation auf: printf() gibt so lange Zeichen aus dem Array aus, bis es auf das abschließende 0-Byte stößt. Wird das vergessen, gibt printf() noch die Dinge hinter dem eigentlichen String aus, also Zeichenmüll... solange, bis zufällig eine Null im Speicher steht. |
| Frage: | Wie erstellt man denn einen String? |
| Antwort: | Es gibt da verschiedene Methoden. Oft füllt man den String nicht sofort mit Text, dann geht es so: C> char text[50]; /* Bsp 1 */ Jetzt haben wir einen String mit dem Variablennamen "text". Er darf maximal 50 Zeichen enthalten, abzüglich des 0-Bytes macht das noch 49 Zeichen sinnvollen Text. Wenn man den String gleich vorbelegen möchte (im obigen Beispiel ist der Inhalt undefiniert, also wieder Zeichenmüll), dann kann man das so tun: C> char text[50] = "Hallo!"; Das ominöse 0-Byte wird dann vom Compiler automatisch erzeugt. |
| Frage: | Wie kann der String aus Beispiel 1 gefüllt werden? |
| Antwort: | Auf keinen Fall darf man das so wie in Amiga-Basic-Zeiten machen:
C> text = "Hallo!"; /* FALSCH! */ Es muß stattdessen eine Funktion aus den Linkerbibliotheken benutzt werden, hier strcpy. Damit der Compiler die Funktion erkennt, muß man "string.h" includen. C> strcpy( text, "Hallo!" ); Der Unterschied zwischen der falschen und der richtigen Variante ist dieser: In beiden Fällen wird eine temporäre Stringvariable (die mit "Hallo" gefüllt wird) vom Compiler angelegt. Im unteren Beispiel wird ein Zeiger darauf an die strcpy()-Funktion übergeben, diese liest den temporären String aus und kopiert seinen Inhalt nach text. Also genau das, was wir wollen. Im oberen Beispiel wird erreicht, daß text sich nun auf die temporäre Variable bezieht (er "zeigt" darauf), und nicht mehr auf den eigentlichen (50 Zeichen langen) Speicherbereich. Temporäre Variablen haben allerdings die Eigenschaft, sofort nach der Ausführung der Anweisung (i.A. der Programmzeile) Harakiri zu begehen, d.h. sie werden wieder destruiert. Deshalb ist über text jetzt höchstwahrscheinlich noch "Hallo!" abrufbar, aber in Wirklichkeit haben wir einen "dangling pointer", also ein Ding womit wir nichts mehr anfangen sollten. Bei strcpy ist zu beachten, daß der Quellstring auch in den Zielstring passen muß. Gegebenenfalls muß man bei der Stringdeklaration mehr Speicherplatz verlangen. |
| Frage: | Wenn ich nun später mal einen größeren String brauche, kann ich dann text noch vergrößeren? |
| Antwort: | Nein. Da sollte man besser eine neue Variable nehmen. Wenn aber dieses Feature wirklich benötigt wird (zum Beispiel in einem Editor, der es erlauben soll, beliebig lange Zeilen zu editieren) muß man auf andere Lösungen zurückgreifen. Man kann den Speicher, in dem ein String abgelegt wird, auch "per Hand" (mit AllocMem, malloc, new oder Konsorten) allozieren (beim System anfordern). Das ist aber schon was für Fortgeschrittene, und für die meisten Fälle reichen die normalen Strings. |
| Frage: | Wollen wir nicht langsam zu einem konkreten Beispiel kommen? Die ganzen strcpys machen mich schon ganz verrückt... |
| Antwort: | Nun gut, hmm äh. Wie wärs, wenn wir ein Programm schreiben, das einige Zeilen Benutzereingabe aufnimmt und sie anschließend wieder zentriert im Shellfenster ausgibt? |
| Frage: | Gar keine Frage! |
| Antwort: | Auf gehts, hier ist das grobe Programmgerüst: |
---8<---
#include <string.h>
#include <stdio.h>
/* Die Dateien enthalten sog. Prototypen, also Erklärungen für den
Compiler, wie Funktionen wie strcpy aufgerufen werden müssen. Damit
er prüfen kann, ob wir alles richtig programmiert haben. Bei C gibt
es nämlich (im Gegensatz zu vielen anderen Sprachen) keine einge-
bauten Befehle, wie PRINT in Basic. Durch das include weiß der
Compiler, daß es eine Funktion wie printf() überhaupt gibt. Und
verwendet sie. Der Linker stellt dann nach dem Compilieren fest,
daß man gar kein printf() definiert hat, und sucht es deshalb in
den Linker-Bibilotheken, wo er im Fall von printf() auch fündig wird. */
char zeilen[20][70]; /* maximal 20 Zeilen a 80 Zeichen. */
int zeilenanz;
void eingabe()
{
// das ist noch zu füllen...
}
void ausgabe()
{
// das ebenfalls
}
void main( )
{
printf("Sie können jetzt maximal 20 Zeilen a 70 Zeichen angeben, wenn Ihnen schon\n"
"vorher die Ideen ausgehen, drücken Sie einfach <Return> in einer leeren\n"
"Zeile.\n");
eingabe();
ausgabe();
printf("Das wars!\n");
// welch ein Musterprogramm!
/* Was hatte eigentlich das \n oben zu sagen? Nun, printf() und Konsorten
können noch diverse Steuersequenzen auswerten. Dazu zählt \n. \n sorgt
für einen Zeilenumbruch im Shellfenster. */
}
--->8---
nun zur Eingabe:
---8<---
void eingabe()
{
short cnt;
for( cnt = 0; cnt < 20; cnt++)
{
gets( zeilen[cnt] );
if( zeilen[cnt][0] == 0 )
break;
/*
* Mit break wird hier ausgestiegen, wenn eine Leerzeile
* eingegeben wurde. Man bemerke die Abfrage des 0-Bytes,
* welches am Stringanfang eines leeren Strings steht.
*/
}
zeilenanz = cnt;
}
--->8---
nun zur Ausgabe:
---8<---
void ausgabe()
{
short cnt, laenge, leeranz, leercnt;
char ausgabestr[80];
for( cnt = 0; cnt < zeilenanz; cnt++ ) {
/* Länge der Zeichenkette feststellen: */
laenge = strlen( zeilen[cnt] );
/* Wir müssen nun die Hälfte der Differenz der Anzahl
der Zeichen des Strings und 70 feststellen! */
leeranz = (70-laenge) / 2;
/* diese Anzahl von Leerzeichen müssen ausgegeben
werden, damit der Text zentriert erscheint. Wir
basteln jetzt einen String, der leeranz Leerzeichen
und die jeweils eingegebene Zeile enthält.*/
for( leercnt = 0; leercnt < leeranz; leercnt++ ) {
ausgabestr[leercnt] = ' ';
}
ausgabestr[leercnt] = 0;
strcat( ausgabestr, zeilen[cnt] );
/* strcat fügt den String in zeile[cnt] an ausgabestr an,
nur zu verwenden bei Strings, in denen man schon ein 0-Byte
gesetzt hat.*/
printf("%s\n", ausgabestr );
}
}
--->8---
Wir hoffen, dieses kleine Programm hat mehr Probleme im Kopf gelöst, als erzeugt. Und vielleicht noch etwas Problembewußtsein geschaffen.
Denn C geht nicht sonderlich intelligent mit Strings um, der Programmierer muß immer mitdenken, was er eigentlich will, und welche Anstrengungen der Compiler tätigen müßte, um sein Vorhaben zu erreichen. Jedem sei gesagt, daß der Compiler zwar schon eine Menge macht, aber alle "unselbstverständlichen" Anstrengungen bleiben letztlich beim Progger...
Anmerkung:
Da in HTML ISO Entities verwendet werden sollte, kann der oben angegebene
Sourcecode nicht direkt verwendet werden. Der Sourcecode liegt dafür im
Lha-Archive dem AmZeiger bei!
![]() |
Inhaltsverzeichnis | ![]() |
| ©`97Der AmZeiger | ||