Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ATOS News-Archiv September 2001


25.09.2001


ARCView: eine Packershell unter der Lupe
Thomas Kerkloh

Man kennt ja das Problem der Beschränkungen von Dateinamen unter normalen TOS-Filesystemen mit der 8+3-Notation. Aussagekräftige Dateinamen werden schnell länger als 8 Zeichen und Leerzeichen im Namen bringen dann auch noch so eigene Probleme mit sich.

Schön, dass es mittlerweile ja auch Alternativen zu diesem Original-Atari-Filesystem gibt, z.B. unter MiNT, unter MagiC, unter MagiCMac, unter MagiCPC, usw.

So sind Dateinamen mit mehr als 11 Zeichen kein Problem mehr und auch Leerzeichen tragen nun zur besseren Identifikation von Dateien bei.

Schade - viele Programme ignorieren diese langen Dateinamen komplett oder teilweise und einige machen aus dem Dateinamen

Das ist nur ein kleinerTest.ZIP

z.B. 6 Einzelfilenamen, da sie am Leerzeichen/Space scheitern.

Dieses Problem tritt in einigen Programmen auf, unter anderem auch bei Packer-Shells.

ARC-View V0.6 vom 28.06.2001, die Packershell von Philipp Donzé soll Abhilfe schaffen.

Bild: Der Info-Dialog vom ARC-View.
(Der Info-Dialog vom ARC-View.)

Was das Programm leistet und wo die Stärken und Schwächen sind, versucht diese Kurzvorstellung zu erläutern. weiter ...


TOSGroup: Standardisierungsgremium gebildet
Uwe Mindrup

Ein Standardisierungsgremium für TOS-kompatible Betriebssysteme hat sich gebildet. Ziel ist es, einheitliche Richtlinien und Standards für Software-Entwickler zu erstellen, um Doppelentwicklungen (z.B. bei Schnittstellen, Oberflächen, etc.) zu vermeiden und die Herstellung neuer Programme zu vereinfachen.

Weitere Informationen: http://www.tosgroup.org.


Omicron.BASIC: Freeware-GEM-Library OM-WINS
Uwe Mindrup

Von Karl-Heinz Bauer stammt eine neue GEM-Library für Omicron.BASIC. Die Features in Kürze:

  • fast vollautomatisch verwaltete Events und GEM-(AES-)Messages
  • komplettes Fenster-Management
  • Schnittstelle (Interface) für programmspezifische Routinen
  • Dialoge modal/unmodal; tastaturbedienbar
  • Listboxen, Karteikarten, Popups in vielen Varianten, Value-Switcher etc.
  • Unterstützung von BubbleGEM und ST-Guide
  • und vieles mehr.

Die Bibliothek ist erst nach vorheriger, kostenloser Registrierung erhältlich.

Neben der eigentlichen Bibliothek OM-WINS gibt es noch diverse, nützliche Hilfsprogramme wie z.B.

  • OM-CHECK: listet Funktionen und Prozeduren sowie deren Aufrufe, alle Konstanten und Variablen (global, lokal) und RSC-Definitionen auf. Damit lassen sich z.B. Tippfehler oder nur einmalig vorkommende Variable leicht finden.
  • OM-RSCDO: generiert einige sehr zeitaufwändige und fehleranfällig zu erstellende, RSC-betreffende Routinen sowohl für Omikron.BASIC als auch für die GEM-Library OM-WINs, um dessen Handling weiter zu vereinfachen und zu beschleunigen.
  • OM-CUT: kann aus einem Omikron.BASIC-Programm-Listing im Gegensatz zum Compiler rekursiv alle nicht benutzten Routinen (Prozeduren und Funktionen) auskommentieren oder löschen.

Weitere Informationen: http://www.people.freenet.de/charly-bauer/. Ausführliche Beschreibungen der Programme finden sich unter http://www.people.freenet.de/charly-bauer/prglist.htm.


21.09.2001


Pinatubo 2.4: nun Freeware
Thomas Kerkloh

Die Pinatubo Version 2.4 ist veröffentlicht und ist nun Freeware.

Michael Schwingen hat die Pinatubo 2.4 - den Treiber und die GEM-Oberfläche für Eprom-Programmierer - als Freeware freigegeben. Sie unterstützt Junior-Prommer, Easyprommer und den 'Vesuv' von Rossmüller. Pinatubo unterstützt bei allen Prommern Megabit- und 16-Bit-Eproms (mit passendem Adapter) sowie EEPROMs und 875x/895x Microcontroller.

Hardwarevoraussetzungen:
ATARI ST/e/TT/Falcon oder Mac mit MagiCMac/McSTout, Eprommer (siehe oben)

Download: http://www.ccac.rwth-aachen.de/~michaels/


Marathon 2.0
Uwe Mindrup

Der Shareware-E-Mail-Client aus dem Draconis-Paket, Marathon, ist nun in der Version 2.0 erschienen. Einige der Neuheiten gegenüber der Version 1.7:

  • Nachrichten nun in Baumstruktur; Drag & Drop zum bequemen Bearbeiten
  • Konfigurationsprofile ("Identitäten")
  • Postfächer nun in einem Favoritenfenster ablegbar.
  • Auto-Vervollständigen von E-Mail-Adressen
  • Nachrichtenfilter
  • Links werden farblich markiert dargestellt
  • Farbiges Quoting
  • beliebig viele Signaturen
  • Externe Programme aufrufbar, z.B. um ein Archiv direkt an einen Entpacker weiterzuleiten
  • etc.

Eine ausführliche Beschreibung der Änderungen und Neuheiten finden sich unter http://draconis.atari.org/draconis/mara2/index.htm. Neben der Shareware-Version gibt es auch eine in vielen Punkten eingeschränkte Demo-Version von Marathon (ca. 1,8 MB) unter http://draconis.atari.org/draconis/mara2/mdownl.htm.


18.09.2001


Neue Draconis-Homepage
Jens Heitmann

Die Draconis-Homepage ist umgezogen. Neue URL ist http://draconis.atari.org. Auch gibt es eine neue E-Mail-Adresse: draconis@atari.org (draconis@atari.org).


13.09.2001


ANSI C - Ein Programmierkurs in mehreren Teilen - Teil XIV
Michael Bernstein

Der nunmehr letzte Abschnitt des Programmierkurses von Michael Bernstein liefert eine kurze Übersicht und Erklärung von Compiler-Optionen der verschiedenen Systeme. weiter ...


Icon Extract 1.2
Florent Lafabrie

Icon Extract 1.2 ist nun in der Version 1.2 erschienen. Es werden nun zusätzlich auch 32-Bit-Icons von XP Windows unterstützt. Die Darstellung klappt nun auch in monochrom und im 16-Farb-Modus des Atari.

Icon Extract erlaubt es, vielerlei Arten von Window-Icons zu extrahieren, so z.B. die Formate ICO, CUR, ANI, ICL. DLL. Alle Icons in 2, 16, 256 und True Color (sowohl 24 als auch 32 Bit) werden in einer RSC-Datei gespeichert. Für jede Icon-Größe (16, 32, 48, 72, 96 und 128 Pixel) wird eine eigene RSC-Datei angelegt.

Die Installation von Icon Extract geschieht mit Hilfe von GEM Setup 2.01.

Sinnvoll ist auf alle Fälle eine schnelle Maschine (68030 + 68881/2) oder Magic PC bzw. Magic Mac.

Für die Version 1.3 von Icon Extract ist die Unterstützung des XPM-Formates aus der Linux-Welt geplant.

Homepage: http://www.club-internet.fr/perso/lafabrie/


STune 0.90
Bernd Mädicke

Das Echtzeit-Strategiegame STune liegt ab sofort in der Version 0.90 vor. Jede Menge Bugs wurden gefixt. Spielstände können nun gespeichert und geladen werden, ein neues Energie Management wurde eingebaut, mehrere Einheiten können gleichzeitig selektiert werden und vieles mehr.

Weitere Informationen: http://stune.atari.org/

Einen ausführlichen Artikel über STune in der Version 0.80 finden Sie im ATOS-Magazin 3/2000


ArcView 0.8 vom 11.09.2001
Bernd Mädicke

Philipp Donzé hat eine neue Version seiner kleinen, aber wirklich feinen Packershell ArcView veröffentlicht. Hier die wichtigsten Neuerungen:

  • Komplett überarbeitete Version.
  • Besseres Look&Feel,
  • neue Funktion Anzeigen,
  • ST-Guide- und BubbleGEM-Hilfen,
  • viele Fehlerbereinigungen...

Läuft nun unter MiNT & Co. (aber immer noch nicht 100%, d.h. beim Setup von Bernd Mädicke klappt es, aber bei den Betatestern noch nicht ganz.)

Homepage: http://home.tiscalinet.ch/donze/

Einen Artikel von Thomas Kerkloh über diese Shell finden Sie unter news20010925-01.html.


XaAES v0.93
Uwe Mindrup

Das Freeware- und OpenSource-Ersatz-AES für (Free)MiNT ist nun in der Version 0.93 erhältlich. Einige der Neuerungen:

  • Der Text für About-Dialoge wurde in eine Listbox gepackt; so können nun auch längere Texte dort problemlos untergebracht werden
  • In alle XaAES-Dialogen gibt es nun OK-Buttons
  • Fehler bei Pfaden (speziell rsrc_load und shel_find) behoben.
  • In ikonifizierten Fenstern ging die Slider-Information verloren. Gefixt.
  • form_alert-Dialoge mit zwei oder weniger Zeilen werden nun wieder korrekt in der Höhe verkleinert.
  • Wenn ein Text in einem Fenstertitel zu lang ist, werden führende und hängende Leerzeichen entfernt und der Text erneut dargestellt.
  • Bei Listboxen wurde manchmal ein Rahmen in weiß gezeichnet, weil eine Farbinformation fehlte.
  • Icons werden nun auch im Vielfarben- und im TrueColor-Mode korrekt gezeichnet dank der Unterstützung von Mario Becroft.
  • Implementation von appl_control
  • Implementation von [ctrl]+[alt]+[tab], [ctrl]+[alt]+[{V|X|Y}]:
    • V: alles wieder hervorholen (unhide)
    • X: alles andere versenken. (hide other)
    • Y: das aktuelle Element versenken (hide own)
  • ... und viele andere Dinge

Homepage: http://xaaes.atari.org/


10.09.2001


Adat 16 in Willebadessen vom 6.-9.12.2001
Thomas Kerkloh

Vom 6.-9.12.2001 findet in Willebadessen das ADAT 16 statt.

ADAT 16 = Allgemeines DTP-AnwenderInnen-Treffen
 weiter ...


aFTP RSC-File nun in deutsch
Bernd Mädicke

aFTP, der FTP-Client von ATACK-Software, ist ja schon ein paar Jährchen alt. Jetzt gibt es endlich ein deutsches RSC-File zu dem Programm. Momentan noch unter dem unten stehenden direkten Link downloadbar, später hoffentlich auch direkt auf der ATACK-Homepage.

Download: http://home.t-online.de/home/BMaedicke/Backup/AFTP_GER.ZIP


MiNT '98 Release 1.2
Bernd Mädicke

MiNT'98 Release 1.2 am Start!

Änderungen seit Rel. 1.0:

  • Aktualisierte MiNT-Kernels und Dateisysteme (Rel. 1.1)
  • Installation auf ext2-Dateisystemen möglich (Rel. 1.1)
  • Unterstützung von ext2-Dateisystemen durch die AdMiNTools? (Rel. 1.1)
  • Fehlerbereinigtes Samba-Paket. Drucken im Netz jetzt problemlos möglich. (Rel. 1.2)
  • Dokumentation jetzt als HTML-Version ausgelagert auf der CD. (Rel. 1.2)

Wichtig: Erst ab 20 Bestellungen wird die CD aufgelegt!

Weitere Informationen: http://www.ag-computer.de/


8.09.2001


Linkseiten aktualisiert
Benjamin Kirchheim

Benjamin Kirchheim hat seine umfangreichen und nach Kategorien eingeteilten Linkseiten aktualisiert. Die Seiten finden Sie

Viel Spaß beim Stöbern!


Archivsammlung erweitert
Benjamin Kirchheim

Benjamin Kirchheim hat seine Atari-Archivsammlung um zwei Archive erweitert:

  • SETFAST.ZIP:
    Mit Setfast lassen sich die FastRAM-Bits von Atari-Programmen verstellen. Im Archiv enthalten sind ein Programm und ein CPX.
  • TTRAM_16.ZIP:
    Hiermit kann ein ausführlicher RAM-Test durchgeführt werden; Defekte RAMs sind oft Ursache von unerklärlichen Abstürzen, besonders auf Ataris mit steckbarem RAM.

Die Atari-Archivsammlung von Benjamin Kirchheim finden Sie unter http://www.bkx.de/download.htm


7.09.2001


ANSI C - Ein Programmierkurs in mehreren Teilen - Teil XIII
Michael Bernstein

Der vorletzte Teil des Programmierkurses von Michael Bernstein befasst sich mit der Include-Datei assert.h, die Hilfen für die Fehlersuche bereitstellt. weiter ...


4.09.2001


Disk-Index 2.44 vom 11.07.01 jetzt auch im Netz der Netze
Bernd Mädicke

Disk-Index, die Datenträger-Katalogisierungssoftware von Alfred Saß steht ab sofort auch im Internet zum Download bereit. Wer Disk-Index noch nicht kennt, hier eine kleine Kurzbeschreibung:

Disk-Index ist eine umfangreiche Datenträger-Katalogisierungssoftware für Disketten, Fest-/Wechselplatten, CD-ROM etc. Die Inhalte der Datenträger werden eingelesen, können angezeigt, verglichen, sortiert und gedruckt werden. Außerdem sind Kommentare zu den einzelnen Dateien möglich, dazu verfügt das Programm auch über eine Kommentarautomatik.

Viele andere Funktionen machen das Programm zu einem mächtigen Werkzeug zur Datenträgerverwaltung. Disk-Index läuft sauber unter GEM und hat auch mit langen Dateinamen keine Probleme.

Download-Möglichkeit: http://home.t-online.de/home/BMaedicke/download.htm.


Atari-Meeting Atari Hody Brno 2001
Petr Marsálek

Für das internationale Atari-Meeting unter dem Namen ATARI HODY (Atari-Fest) gibt es ab sofort neben den tschechischen nun auch deutsche und englische Beschreibungsseiten.

Weitere Informationen:
http://www.atari.kgb.cz/ahody/2001/ahody_d.htm (deutsch)
http://www.atari.kgb.cz/ahody/2001/ahody_e.htm (englisch)


1.09.2001


VISION 4.0e
Bernd Mädicke

Die Bildbearbeitungssoftware Vision aus Frankreich ist in der Version 4.0e erschienen. Neu ist unter anderem:

  • neue Version des JPEG-(DSP)-Decoders von Brainstorm, der besser für CT2-User geeignet ist.
  • Die Bildskalierungsfunktion arbeitet schneller
  • die Echtzeitlupe arbeitet bis zu fünfmal schneller

Weitere Informationen: http://www.multimedia.com/jlusetti/telee.htm


STiK 2.03 und Qdialer 0.51
Bernd Mädicke

Dan Ackerman hat die Version 2.03 von STiK veröffentlicht. Neu ist die Unterstützung von ACTIVE_PPP: damit wird STiK dazu veranlasst, PPP-Negotation sofort beim Einwählen zu verwenden.

Der Qdialer wurde auf die Version 0.51 aktualisiert. Neu ist die Anpassung an ACTIVE_PPP. Außerdem wurden einige Bugs gefixt.

Weitere Informationen: http://www.netset.com/~baldrick/stikdl.html


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

25.09.01

ARCView

Eine Packershell unter der Lupe

Von Thomas Kerkloh

Man kennt ja das Problem der Beschränkungen von Dateinamen unter normalen TOS-Filesystemen mit der 8+3-Notation. Aussagekräftige Dateinamen werden schnell länger als 8 Zeichen und Leerzeichen im Namen bringen dann auch noch so eigene Probleme mit sich.

Schön, dass es mittlerweile ja auch Alternativen zu diesem Original-Atari-Filesystem gibt, z.B. unter MiNT, unter MagiC, unter MagiCMac, unter MagiCPC, usw.

So sind Dateinamen mit mehr als 11 Zeichen kein Problem mehr und auch Leerzeichen tragen nun zur besseren Identifikation von Dateien bei.

Schade - viele Programme ignorieren diese langen Dateinamen komplett oder teilweise und einige machen aus dem Dateinamen

Das ist nur ein kleinerTest.ZIP

z.B. 6 Einzelfilenamen, da sie am Leerzeichen/Space scheitern.

Dieses Problem tritt in einigen Programmen auf, unter anderem auch bei Packer-Shells.

ARC-View V0.6 vom 28.06.2001, die Packershell von Philipp Donzé soll Abhilfe schaffen.

Bild: Der Info-Dialog vom ARC-View.
(Der Info-Dialog vom ARC-View.)

Was das Programm leistet und wo die Stärken und Schwächen sind, versucht diese Kurzvorstellung zu erläutern.

Inhaltsverzeichnis:


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Die Packer

Packer wurden von jeher eingesetzt - Plattenplatz, resp. Diskettenplatz war in den Anfängen knapp oder nicht vorhanden, weil teuer. Da lag es nahe, Daten zu komprimieren, um so wertvollen Platz zu sparen. Im Falle von Diskettenzugriffen war sogar die Zeitersparnis beim Laden/Speichern ein Argument für das Packen/Komprimieren von Daten.

Es gab und gibt die verschiedensten Arten von Packern - weit verbreitet, gerade im Atari-Sektor, sind ZIP und LZH. Im Bereich der Unixe (dazu zählt man auch im weitesten Sinne MiNT) sind es GNU-Zip (GZIP) und TAR, wobei TAR als eigenständiger Packer nur Dateien aneinander heftet. Im Regelfall wird nach dem Zusammensetzen diese Datei diese noch gepackt. Ebenfalls ein Regelfall: Die Packart ist das GNU-ZIP, die Dateiextension *.tgz.

Auch wenn die Methode der Komprimierung und/oder der Archivierung, respektive die Packalgorithmen von Packer zu Packer variieren: unterscheiden muss man verlustfreie und verlustbehaftete Packarten.

verlustfrei vs. verlustbehaftet

Packer für binäre Programm-/Daten-Dateien müssen verlustfrei arbeiten, da man ja die Programme/Daten noch genauso benötigt, wie sie vor dem Packen waren. Neben ZIP, TAR, ZOO und Konsorten sind auch GIF, LZW-TIFF und HYP gepackte Dateien - ein Grund, warum es wenig Sinn macht, diese Dateien nochmals zu packen.

Verlustbehaftete Packer sind zum Beispiel MP3 und JPG.

Mehr Informationen zu Packern gerade auf dem Atari-Computern findet man

Details in Form von Beschreibungen und teilweise auch Sourcen der diversen Formate findet man unter http://www.wotsit.org/.

Eine Kurzerläuterung zu einigen Arten der hier genannten Archive findet man im Kapitel Archivarten.

Dass die meisten Packer ursprünglich aus der Welt der Kommandozeilen-Systeme stammen, sieht man an der Programmart. Im Regelfall kommen sie auch auf dem Atari als kommandozeilen-orientiertes Programm als TTP/TOS oder GTP zum Einsatz.

Grafische Interfaces a la STZIP 2.60 für ZIP oder ESS UUEncode/UUDecode 4.0 für UUE sind die Ausnahme; sie dienen auch nur als Frontend für den eigenen Packer/Entpacker.

Den Umgang mit der großen Anzahl an Packer/Entpackern auch auf dem Atari vereinfachen dann die dementsprechende Shells wie z.B. ARC-View und die in dem Kapitel Die Alternativen genannten Shells.

Diese Shells sind auch in der Lage, von sich aus diverse Archiv-Arten zu erkennen, deren Inhalt selbst auszulesen und aufzulisten und so dem User einen Überblick über die in dem Archiv enthaltenen Daten zu geben.

Der einfache Zugriff auf die Möglichkeiten Entpacken, Packen, Löschen und evtl. spezielle Kommandos runden die Leistungen ab.

Zugriffspfad und Kommandos der Packer/Entpacker werden der Shell mitgeteilt und diese ruft nun mit den dementsprechenden Kommandos die einzelnen Programme auf.

Gibt es eine neue Version eines Packers/Entpackers, so tauscht man nur dieses Programm aus (neue Pfade/neuen Namen muss man dann natürlich der Shell mitteilen), ändern sich die Kommandos, so werden sie ebenfalls in der Shell neu eingegeben.


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Status

Arc-View ist Freeware.

Hier ein Auszug aus dem README.TXT:

Rechtliches
---
Dieses Programm ist FREEWARE, d.h. jeder darf das Programm an alle Bekannten/Verwandten/Freunde/Kollegen weitergeben, solange er die Dateien in unveränderter Form weitergibt.

Der Status Freeware gilt für die Packer-Shell ARC-View, es gilt nicht für die noch benötigte Zusatzsoftware (Packer/Entpacker). Dort muss bei jedem Programm nachgeschaut werden, wie es mit dem Status aussieht.



Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Systemvoraussetzungen

  • MagiC (der AES-Befehl shel_write(SHW_PARALLEL) wird benötigt)
  • etwas Speicher
  • einen AV-Server (z.B. Jinnee)
  • die diversen Packer/Entpacker wie z.B. ZIP, TAR, usw.

Testsystem

ARCView wurde auf der folgenden Systemumgebung getestet:

Rechner Apple Macintosh PPC-Klone von Umax mit 266Mhz-G3-Prozessorkarte
Betriebssystem MagiCMac 6.1.4 mit 120 MB durchgehenden/ST-Ram
Bildschirmauflösungen und Farbtiefe von 640*400 bis 1280*960, 16, 256 Farben und TC.
sonstige installierte Software NVDI 5.0.3, Jinnee 2.0, Ratsch, ST-Guide, 1ST-Guide, GrafTool, Papyrus (DEMO), AniPlayer, OLGA, SMU, die laut LIESMICH.TXT/README.TXT aufgeführten Packer/Entpacker usw.

Installation

Das Archiv wird in an einem beliebigen Ort entpackt; es sollten dann folgende Dateien vorhanden sein:

ARCVIEW.APP Das eigentliche Programm.
ARCVIEW.CFG Die Konfigurationsdatei ist eine reine ASCII-Datei und kann somit bei entsprechender Kenntnis auch per Text-Editor angepasst werden.
ARCVIEW.RSC Das RSC-File, in deutsch.
HISTORY.TXT Die Auflistung über die Änderungen.
LIESMICH.TXT Eine kurze, aber ausreichende deutsche Erklärung über das Programm.
\ENGLISH\ARCVIEW.RSC Das RSC-File, in englisch.
\ENGLISH\README.TXT Eine kurze, aber ausreichende, englische Erklärung über das Programm.

Nun meldet man ARCVIEW.APP für die Datei-Extension *.ZIP, *.LZH (inkl. *.LHA und *.LZS; ARC-View erkennt sie korrekt als LZH), *.ARC, *.ARJ, *.ZOO, *.TAR im Desktop an.

Beispielsweise in Jinnee:

ARCVIEW.APP selektieren, das Menue Sonstiges ausklappen, den Menüpunkt Applikationen anwählen, die Frage, ob man ARCVIEW.APP neu anmelden möchte, mit JA beantworten und unter dem Eintrag Öffnen die erwünschten Dateiextensionen eintragen.

Über das Menue Sonstiges und dem Menüpunkt Desktop sichern... wird diese Eintragung auch in die INF-Datei übernommen und steht somit künftig auch nach Neustarts zur Verfügung.

Zum Beispiel in MagXDesk

ARCVIEW.APP selektieren, das Menue Optionen ausklappen, den Menüpunkt Anwendung anmelden... anwählen, den nun erscheinenden Dialog mit OK bestätigen und im nachfolgenden Dialog ARCVIEW.APP im linken Fenster selektieren. Den Button Neu... unter dem rechten Fenster anklicken und dort in die 4 Felder die ersten 4 Dateiextensionen eintragen.

Nochmals den Button Neu... unter dem rechten Fenster anklicken und dort in die 4 Felder die fehlenden Dateiextensionen eintragen.

Unter dem Menue Optionen den Menüpunkt Arbeit sichern anwählen und den nachfolgenden Dialog mit OK bestätigen, damit die Eintragungen auch beim nächsten Start der MagXDesk eingetragen sind.

Per Doppelklick auf einen dieser Dateitypen (oder Doppelklick auf das Programm) wird nun ARCVIEW.APP gestartet und man kann nach dem Erststart mit den Einstellungen innerhalb dieses Programms fortfahren. Alternativ kann man auch eine Datei auf das Icon von ARCVIEW.APP draggen.



Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Einstellungen

Nach dem ersten Start von ARCVIEW.APP sollte man im Menue unter Optionen den Menüpunkt Einstellungen... aufrufen und im Folgenden Dialog die Einstellungen an seine Bedürfnisse anpassen.

Bild: Der Dialog Einstellungen...
(Der Dialog Einstellungen...)

Die hier zu findenden Möglichkeiten sind klar gegliedert und bedürfen eigentlich keiner weiteren Erläuterung.

Etwas mehr Aufwand steckt im nächsten Dialog, zu finden unter dem Menue Optionen den Menüpunkt Pfade konfigurieren....

Bild: Der Dialog Pfade konfigurieren...
(Der Dialog Pfade konfigurieren...)

Für jeden Packer/Entpacker und für jede Aktion (Entpacken, Packen, Löschen) einzeln sind diese Angaben einzustellen (ein Klick auf den Button Entpacken bringt ein Popup zum Vorschein...).

Bild: Der Dialog Pfade konfigurieren... mit dem Popup für die Einzelaktionen
(Der Dialog Pfade konfigurieren... mit dem Popup für die Einzelaktionen)

Das Konfigurieren ist zwar ein recht hoher Aufwand, eröffnet einem aber auch die Möglichkeit, für jedes Packformat ein eigenständiges Programm für jede Aktion zu wählen und ARC-View gezielt auf dessen Bedürfnisse vorzubereiten.

Positiv fällt auf, dass die Konfigurationsdatei ARCVIEW.CFG eine reine ASCII-Datei ist - geübten Usern steht so auch der Weg der manuellen Änderungen nichts im Wege. Ein gründliches Studium der Parameter und deren Bedeutung sollte dem aber vorausgehen, da die verschiedenen Parameter für die verschiedenen Packer/Entpacker nicht für jeden User gängig sind. Nur gut, dass der Autor die Parameter für die gängigsten Programme schon integriert hat und die Parameterbedeutungen im Text LIESMICH.TXT recht detailliert aufgeführt sind.

Zur Zeit werden folgende Packer/Entpacker von ARC-View direkt mit voreingestellten Parametern unterstützt:

  • ZIP = Zip 2.3 und Unzip 5.4 (ST-ZIP ist damit nicht gemeint, da dieser u.a. keinen langen Dateinamen beherrscht!
  • LZH = Lharc 3.13
  • ARC = Arc-ST 6.02
  • ARJ = ARJ 9.96 (von der TOS-Crew)
  • ZOO = Zoo 2.1
  • TAR = GNU Tar 1.13

Diese Programme müssen natürlich zusätzlich zu ARC-View installiert sein und dementsprechend in ARC-View konfiguriert werden.

Sind alle Einstellungen getätigt, kann es losgehen...



Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Praxisbetrieb

Ist ARCVIEW.APP als zuständiges Programm für die Dateiextensionen *.ZIP, *.LZH (*.LZS, *.LHA), *.ARC, *.ARJ, *.ZOO, *.TAR eingetragen, so reicht ein Doppelklick auf einen dieser Dateitypen, um ARC-View zu starten. Alternativ kann man ARC-View so starten und unter dem Menue Datei den Menüpunkt Öffnen... eine Datei manuell auswählen, eine Datei auf ARCVIEW.APP draggen, oder den Desktop so konfigurieren, dass er vor dem Start von ARCVIEW.APP eine Eingabeaufforderung anbietet.

Egal, welche Möglichkeit der User gewählt hat: ist die übergebene Datei ein Archiv, das ARC-View erkennt, so öffnet das Programm ein Fenster mit dem Inhalt des Archives.

Bild: ein von ARC-View geöffnetes Archiv
(ein von ARC-View geöffnetes Archiv)

Es wird direkt nach dem Öffnen eines Archives nur die oberste Ebene des Verzeichnisses dieses Archives dargestellt. Benutzer des Desktops Jinnee werden zwar etwas irritiert sein, aber per Doppelklick auf das Dreieck vor einem Ordnernamen wird in diesen Ordner hineingesprungen und die dort befindlichen Dateien/Ordner im gleichen Fenster dargestellt.

Bild: ein Sprung in einen Ordner im Archiv
(ein Sprung in einen Ordner im Archiv)

Das Packen/Entpacken/Löschen läuft jetzt genauso ab wie im Desktop: Dateien/Ordner packen und auf das Ziel draggen.

Bild: eine reguläre Kopieraktion...
(eine reguläre Kopieraktion...)

Bild: Dateihandling á la Desktop
(Dateihandling á la Desktop)

Beim Auspacken werden die Größen mit angezeigt, beim Packen nicht.

Bild: keine Größenberechnung beim Packen
(keine Größenberechnung beim Packen)

Nach dem Bestätigen der dementsprechenden Aktion durch Klick auf den OK-Button startet ARC-View das dementsprechende Programm nach. Unter MagiC wird dann VT52 mit dem Programm gestartet.

Bild: Programme unter MagiC laufen im VT52
(Programme unter MagiC laufen im VT52)



Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Packertest

Die Tests für ARC-View beschränkten sich nicht nur auf das Programm selber, auch die Packer/Entpacker mussten sich einem Praxistest stellen.

Die nachfolgend aufgeführte Ordnerstruktur nebst Dateien wurde generiert und den diversen Packern/Entpackern zum Einpacken/Auspacken vorgesetzt.

  • \leer\
  • \ordner\
  • \ordner\text mit leerzeichen drin.txt
  • \ordner\textkurz.txt
  • \ordner\textlangername.txt
  • \ordner mit leerzeichen\
  • \ordner mit leerzeichen\text mit leerzeichen drin.txt
  • \ordner mit leerzeichen\textkurz.txt
  • \ordner mit leerzeichen\textlangername.txt
  • \ordnermitlangennamen\
  • \ordnermitlangennamen\text mit leerzeichen drin.txt
  • \ordnermitlangennamen\textkurz.txt
  • \ordnermitlangennamen\textlangername.txt
  • \text mit leerzeichen drin.txt
  • \textkurz.txt
  • \textlangername.txt

Probleme der einzelnen Packer/Entpacker sind im Kapitel Stärken und Schwächen der Packer aufgeführt.

Auch auf dem Atari bleibt eigentlich nur ZIP (in der Version, die von Philipp Donzé portiert wurde) als Packer/Entpacker für User mit langen Dateinamen, alle anderen haben mehr oder weniger ausgeprägte Schwächen/Defizite.


Bild: Datentransfer per Archive zu diversen Systemen
(Datentransfer per Archive zu diversen Systemen)

Andere Länder, andere Sitten... pardon... andere Packer. So ganz stimmt das nicht, eigentlich sollen sich gerade Archive nicht unterscheiden, sondern auf jedem System vom passenden Packer/Entpacker bearbeiten lassen.

Bei den diversen Tests offenbarte sich allerdings, dass es nicht so ist, gerade der Apple Macintosh ist da nicht sonderlich kompatibel bzw. einige Programme sind kaum einsetzbar.

Aber auch auf den anderen Systemen (Atari-Systeme eingeschlossen) sieht das Ergebnis der diversen Tests teilweise sehr ernüchternd aus. Angefangen von Problemen bei Archiven mit langen Dateinamen und/oder Leerzeichen im Dateinamen über nur unvollständig auspackbare Archive bis hin zum Unvermögen, die Archive zu erkennen, reicht die Palette der Erfahrungen.

Das Ziel der diversen Tests war es, einen Datenaustausch von und zum Atari zu gewährleisten, also Archive auf den verschiedenen Systemen zu erstellen und untereinander wieder auszupacken.


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Das Fazit des Testes

  • ZIP ist das Format der Wahl, wenn es um den reinen Datentransfer geht, es macht am wenigsten Probleme und ist auf jedem Rechner verfügbar.
  • TAR ist das Format der Wahl, wenn man Rechte mit übernehmen will, allerdings muss man dann auch etwas Zeit in die Suche und Installation der geeigneten Versionen für die Systeme investieren.
  • RARen oder ARJen einer geZIPten (oder geLHArcten oder geZOOten) Datenmenge heißt es, wenn man seine großen Datenmengen (groß meint: mehr Daten, als das Wechselmedium fasst) transportieren muss.
  • RARen oder ARJen einer geTARte Datenmenge heißt es, wenn man seine großen Datenmengen (groß meint: mehr Daten, als das Wechselmedium fasst) transportieren und man die Rechte der Dateien und Ordner mitnehmen muss.

Für alle Systeme gilt:

  • Immer den aktuellen Packer/Entpacker nehmen!
  • Auf neue Features verzichten, wenn sie auf anderen Systemen nicht existieren!


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Die Alternativen

Wenn es um lange Dateinamen geht, führt kein Weg an ARC-View vorbei, es ist schlicht die einzige Packer-Shell, die damit keine Probleme hat.

Kann man auf die langen Dateinamen verzichten und hat unter Umständen auch keine Multitasking-Umgebung mit einem geeigneten AV-Server, so bieten sich noch 3 weitere Packer-Shells an, die für diese Zwecke zeitgemäß daherkommen:

Two in One

Bild: Der Info-Dialog von 2in1.
(Der Info-Dialog von 2in1.)

Etwas älter, aber auch ausreichend für Archive in der 8+3-Notation. Die beiden Seiten des Shellfensters erlauben allerdings nur 8+3 Filenamen und auch der Aufruf der Dateiauswahl führt in eine 8+3-Domain.

Bild: Die Oberfläche von 2in1.
(Die Oberfläche von 2in1.)

Ruft man 2in1 mit gedrückter Shift-Taste auf, so wird das Archiv direkt mit den erforderlichen Parametern an den Packer/Entpacker weitergereicht. Im Falle von Zip 2.3 kann man so auch lange Dateinamen packen/entpacken lassen, solange das Start-Archiv/der Startordnername der 8+3-Konvention entspricht und der Packer/Entpacker mit langen Dateinamen umgehen kann.

Unterstützt:

  • LHARC = LZH/LZS/LHA
  • STZIP = ZIP
  • ZOO
  • ARC
  • UNARJ
  • VD-Quick

Bis zu 8 weitere Packer/Entpacker lassen sich aber zusätzlich einbinden über den Menüpunkt Optionen Programme....

Pac-Shell

Bild: Der Info-Dialog der Pac-Shell.
(Der Info-Dialog der Pac-Shell.)

Nicht ganz so alt wie die 2in1-Shell, kann aber auch nicht mit langen Dateinamen umgehen. In Gegensatz zur 2in1-Shell mit den beiden Verzeichnisfenstern kommt die Pac-Shell als Desktopersatz daher und erlaubt intuitives Arbeiten.

Bild: Die Oberfläche von der Pac-Shell.
(Die Oberfläche von der Pac-Shell.)

Unterstützt:

  • LHARC = LZH/LZS/LHA
  • STZIP = ZIP
  • Diffy = DIF
  • RAR
  • ZOO
  • ARC
  • SIT
  • ARJ

Cogito

Bild: Der Info-Dialog von Cogito
(Der Info-Dialog von Cogito)

Noch etwas älter, reicht aber für den Hausgebrauch. Als Shell ist sie eine reine Startershell, d.h. sie kann die Inhalte der Archive, die sie managt, nicht auslesen und darstellen!

Bild: Die Oberfläche von Cogito
(Die Oberfläche von Cogito)

FlexiARC 3.1a

Bild: Der Info-Dialog von FlexiARC
(Der Info-Dialog von FlexiARC)

Eine ältere Software, die nicht gerade zeitgemäß ist: sie stammt aus dem Jahr 1993, läuft unter MagiCMac nur vernünftig als SingleTask-Applikation.

Man kann 5 Packer/Entpacker einbinden.

Bild: Die Oberfläche von FlexiARC
(Die Oberfläche von FlexiARC)

UNPACK.TTP

Bild: Die Startmeldung des TTP
(Die Startmeldung des TTP)

Eigentlich keine richtige Shell, kein bisschen grafisch orientiert, allerdings erfüllt sie ihre Aufgabe als reiner UNPACK-Manager sehr gut und ist noch beliebig an neue/andere Entpacker anpaßbar.

Einpacken geht allerdings nicht...



Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Stärken und Schwächen der Packer

Unterschiede muss man zwischen Stärken/Schwächen von ARC-View und den Stärken/Schwächen von den dementsprechenden Packern/Entpackern machen; letztere beschreibt Philipp Donzé, der Autor von ARC-View auch im LIESMICH.TXT:

Anmerkung: die kursiv gesetzten Kommentare stammen von Thomas Kerkloh, dem Autor dieses Artikels.

Einschränkungen der Packern/Entpackern

Die mitgelieferte Konfiguration wurde nach bestem Wissen erstellt. Trotzdem gibt es bei einigen Programmen gewisse Einschränkungen:

ARC 6.02ST

  • Entpacken klappt nur im Root-Verzeichnis
  • Packen und Löschen ohne Einschränkungen

nach meinen Tests kann diese Version keine Dateien einpacken, die lange Dateinamen haben!
Quoting bringt auch keine Abhilfe!

ARJ 9.96B

  • Entpacken ohne Einschränkungen
  • Packen löscht immer bisherigen Inhalt des Archivs (=kein Hinzufügen). Außerdem können keine leeren Verzeichnisse gepackt werden.
  • Löschen klappt gar nicht! (keine Funktion gefunden)

nach meinen Test kann diese Version keine Dateien einpacken, die lange Dateinamen haben!
Quoting bringt auch keine Abhilfe!

Lharc 3.13a

  • Alle Funktionen ohne Einschränkungen
  • (Leere Verzeichnisse werden nicht eingepackt)
  • (wird die letzte Datei aus dem Archiv gelöscht, so wird die Archiv-Datei ebenfalls gelöscht!)

Mit dem Einpacken von Ordner und/oder Dateien mit Leerzeichen im Namen gibt es Probleme; seltsamerweise kann der Packer unter der Pac-Shell Ordner einpacken, in dem Files liegen, die Leerzeichen im Namen haben...
Quoting bringt auch keine Abhilfe!

Zip 2.3

  • Alle Funktionen ohne Einschränkungen (bei verschlüsselten Archiven wartet das Unzip.ttp auf die Eingabe des Passwortes, ohne dies dem User anzuzeigen. -> Einfach das Passwort blind eintippen. (Ist seit der neuen Version von unzip.ttp behoben!))

Zoo 2.1

  • Entpacken klappt nur für aktuelles Verzeichnis und eine Ebene tiefer
  • Packen (nur Dateien, keine leeren Verzeichnisse)
  • Löschen klappt nur für aktuelles Verzeichnis und eine Ebene tiefer
  • Entstehen durch das Löschen leere Verzeichnisse, so werden diese auch aus dem Archiv entfernt!
  • Nach meinen Tests kann diese Version keine Dateien einpacken, die lange Dateinamen haben!
    Quoting bringt auch keine Abhilfe!

Tar 1.13 (GNU Tar)

  • Alle Funktionen ohne Einschränkungen, jedoch nur für das Packen bzw. Entpacken ins Verzeichnis, in dem die TAR-Datei selber liegt. Diese Einschränkungen gilt, da das benutzte TAR Programm keine absoluten Verzeichnisse verarbeiten kann.

Zur Zeit funktioniert bei mir nur das Auspacken eines Archives, Einpacken/Löschen führt zu defekten Archiven=ARCView erkennt sie nicht mehr als TAR, obwohl es keine Veränderungen gegeben hat!



Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Stärken und Schwächen von ARCView

Kommen wir jetzt zu den Punkten, die ARCView direkt betreffen:

Stärken von ARCView

  • keine Probleme mit langen Dateinamen und Leerzeichen im Dateinamen
  • kompakt
  • gute Integration in den Desktop
  • deckt die gängigsten Packer ab
  • auch der Packer TAR wird unterstützt
  • für jede Aktion (Packen, Entpacken, Löschen) kann ein spezielles Programm gewählt werden
  • Freeware
  • der Autor ist per E-Mail erreichbar ;-)

Schwächen

  • Selbstextrahierende Archive (*.SFX) können teilweise nicht erkannt werden.

    Je nach SFX-Generator können diese Dateien nicht als Archive von ARC-View erkannt werden:

    • SFX.PRG (SFX_LZH - ST V1.6 (C) 1989 by Stefan Gross LZH (algorithm by Haruyasu Yoshizaki) wird nicht korrekt erkannt
    • einige DOS/Windows-Versionen von Packern, die selbstextrahierende Files generieren, werden ebenfalls nicht korrekt erkannt (z.B. LHA's SFX 2.13L (c) Yoshi, 1991)
    • MAKE_SFX.TTP (LHarc SFX-Maker v3.10, (c) Christian Grunenberg, May 25 1994) wird korrekt erkannt
    • ZIP2TOS.PRG (STZip self extracting file v2.6 - (c) Vincent Pomey '93-94) wird korrekt erkannt
  • es können keinen neuen Archive erstellt werden
  • Einpacken neuer Files in ein Archiv nur in das Root-Verzeichnis des Archivs (ist das ein Problem von ARC-View oder der/des Packers??)
  • läuft nur unter MagiC
  • das Archivfenster springt nach Pack/Entpack/Löschaktionen immer wieder in die linke obere Ecke des Desktops
  • nicht direkt um neue Archivarten erweiterbar
  • keine Sizer an den Fenstern mit den Archiven (Bei langen Archivnamen wäre das recht nützlich, und bei leeren Archiven gibt es kein Minimalfenster mehr, das kaum bedienbar ist).


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Wünsche

Sowohl die Anwender als auch der Autor der ARC-Shell haben Wünsche,hier eine kurze Aufstellung von Features/Wünschen beider Seiten:

  • Fenster/Dialoge-Position
    • öffnen an der Mausposition
    • zentriert
    • Position merken
  • Eine Icondarstellung der Dateien/Ordner im ARC-View-Fenster á la Desktop/Jinnee, ein-/ausschaltbar wie bei Jinnee
  • Anlegen von neuen/leeren Archiven
  • Anlegen eines Verzeichnisses mit dem Archivnamen beim Auspacken, als Option ein-/ausschaltbar
  • direktes Starten von Dateien aus dem ARC-View-Archiv-Fenster (d.h.: Doppelklick auf eine Datei im Archiv führt zum Auspacken in ein temporäres Verzeichnis und dann wird diese Datei dem Desktop übergeben, der dann das eingetragene Programm für diese Extension startet)
  • weitere Standard-GEM-Kommandos wie z.B. Control&A für alles selektieren, Control&C für kopiere markierte Dateinamen ins Clipboard, usw.
  • dynamisches Erweitern der Archivarten (z.B. GZIP, RAR, HQX, SIT, usw.) über die cfg-Datei.
    Beherrscht ARC-View dieses Format nicht, so wird dies im Fenster mit einem Symbol oder mit rotem Text dargestellt, evtl. auch ein Warnhinweis. Als Name wird in dem Fall der Filename genommen und wenn man diese nun per D&D auspacken will, wird in dem Zielverzeichnis ein Ordner mit dem Namen des Files angelegt und der eingetragene Packer/Entpacker mit den eingetragenen Parametern gestartet.
  • MiNT(nAES)-Anpassung
  • BubbleGEM/STGuide-Unterstützung
  • Berechnung der Datenmenge bei Packvorgängen, als Option ein-/ausschaltbar
  • Anzeige der Dateidaten wie Länge, Datum, Zeit, Flag à la Desktop-Anzeige, als Option ein-/ausschaltbar
  • Iconleiste à la Luna/WinZIP, als Option ein-/ausschaltbar
  • multiple Fensteransichten eines Archives, um innerhalb dieses Archives hin und her zu kopieren
  • Kopieren von einem Archiv in ein anderes
  • Fenstersizer, damit man sich den Dateinamen der Datei ansehen kann, wenn dieser länger ist als die Namen der Dateien, die sich im Archiv befinden
  • automatisches Aktualisieren der Ansicht eines Fensters nach Pack/Enpack/Löschaktionen und/oder ein manuelles per Taste ESC
  • Kontext-Menü für die Kommandos Entpacken, Löschen, Information, Starten
  • Hintergrundbedienung der Fenster

Ob und was und wann an Features in das Programm eingebaut wird, liegt neben dem Faktor Zeit wohl auch etwas an der Resonanz der User, über die sich Philipp Donzé, der Autor von ARC-View sicherlich freut.



Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Schöne Aussichten (Die Betaversion)

Der Titel dieses Kapitel verrät es schon - es gibt eine Weiterentwicklung von ARC-View. Aktuell lag zu dem Zeitpunkt, an dem dieses Kapitel geschrieben wurde, die Beta 0.8 vom 14.08.2001 vor (Hinweis: mittlerweile hat Philipp Donzé die Version ARC-View 0.8 offiziell freigegeben, siehe Bezug aus dem Internet. Red.).

Neu hinzugekommen sind folgende Features:

  • Anzeige der Dateidaten wie Länge, Datum, Zeit, Flag a la Desktop-Anzeige, als Option ein-/ausschaltbar
    Unter dem Menupunkt Inhalt findet man nun u.a. die Menueinträge Dateigröße, Uhrzeit und Datum, die einzeln ein- und ausgeschaltet werden können.

    Bild: ausführlichere Informationen über Dateien. Die Einstellungen werden direkt im Fenster eines geöffneten Archives übernommen.
    (ausführlichere Informationen über Dateien. Die Einstellungen werden direkt im Fenster eines geöffneten Archives übernommen.)

  • Aufklappen der Ordner à la Jinnee
    Wie man hier sehr schön sieht, können die Ordner durch Klick auf die Dreiecke nun aufgeklappt werden. Jinnee lässt wieder einmal grüßen.

    Bild: Es gibt nun aufklappbare Ordner.
    (Es gibt nun aufklappbare Ordner.)

  • Anlegen von neuen/leeren Archiven
    Per Control&O (für Datei öffnen) wird in der Dateiauswahlbox der gewünschte Dateiname des neuen Archives eingegeben. Die Nachfrage, ob man ein neues Archiv anlegen möchte, beantwortet man mit JA....

    Bild: so legt man neue/leere Archive an...
    (so legt man neue/leere Archive an...)

    .... und schon hat man ein neues leeres Archiv.

    Bild: Neues Archiv ohne Probleme.
    (Neues Archiv ohne Probleme.)

  • automatisches Aktualisieren der Ansicht eines Fensters nach Pack/Entpack/Löschaktionen und/oder ein manuelles per Taste ESC
    Soweit wie möglich erfolgt nun nach einigen Aktionen ein Neueinlesen, per ESC kann es jederzeit auch erzwungen werden.
  • Fenstersizer, damit man sich den Dateinamen der Datei ansehen kann, wenn dieser länger ist als die Namen der Dateien, die sich im Archiv befinden

    Bild: Neues Archivfenster mit Slider, um es größer zu ziehen.
    (Neues Archivfenster mit Slider, um es größer zu ziehen.)

  • BubbleGEM/STGuide-Unterstützung
    BubbleGEM wird nun in den Einstellungsdialogen genutzt, kurze Info-Sprechblasen geben nun Hilfestellung.

    Bild: BubbleGEM wird nun unterstützt.
    (BubbleGEM wird nun unterstützt.)

    Als weitere Hilfestellung bekommt der User auch den Hypertext für ST-Guide, dieser basiert auf den Anleitungstexten und ist bebildert.

  • weitere Tastaturshortcuts
    Neben der ESC-Taste, die eine Aktualisierung der Archivfenster vornimmt, wird nun auch die Tastenkombination Control&A unterstützt. Sie bewirkt eine Selektion aller Dateien und Ordner in dem geöffneten Archivfenster. Per Control&C wird eine Liste der markierten Dateien in ein Archiv als Text(SCRAP.TXT) in das Clipboard geschrieben.
  • direktes Starten von Dateien aus dem ARC-View-Archiv-Fenster
    In ARC-View als Anzeigen unter dem Menupunkt Optionen unter dem Menueintrag Pfade konfigurieren... integriert.

    Bild: Anzeige von Dateien aus dem Archiv heraus.
    (Anzeige von Dateien aus dem Archiv heraus.)

  • MiNT(nAES)-Anpassung
    Erste Erfolge sind schon sichtbar, weitere Betatests aber noch notwendig.

Geändert wurde:

  • das Archivfenster springt nach Pack-/Entpack-/Löschaktionen nun nicht mehr immer wieder in die linke obere Ecke des Desktops
  • Erweiterung der Optionsparameter, die von ARC-View zur Verfügung gestellt werden um die Kommandozeile zu generieren.
  • Erweiterung der Erkennung von selbstextrahierenden Archiven.
  • diverse weitere Änderungen und Bugfixes

Wie man sieht, es geschieht einiges an ARC-View. Der Autor Philipp Donzé ist bestrebt, sein Programm noch weiter zu verbessern und an die Bedürfnisse der User anzupassen. Die Beta-Version läuft im Großen und Ganzen rund, Fehler werden, soweit dem Autor bekannt gemacht, sehr schnell eliminiert. Neue Features werden ebenfalls weiter eingebaut. Ob es eine neue Version zum Zeitpunkt der Veröffentlichung dieses Artikels gibt, kann man unter den URL in dem Kapitel Bezug erfahren.



Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Fazit

Es überwiegen die Stärken des Programms, die Nachteile sind so gering und stören die Funktion nicht im geringsten. Das Programm fügt sich quasi nahtlos in den jeweiligen Desktop ein und bietet dem User genügend Unterstützung, um seine diversen Archive komfortabel auszupacken. Abstürze sind während meiner Testphase auf dem Testsystem nicht aufgetreten.

Die Beta-Version macht einen noch besseren Eindruck und die neuen Features sind gelungen.

Inzwischen hat ARC-View (Beta 0.8) die bisherige Packershell des Artikelautors (Two in One) nahezu komplett ersetzt.

Ungereimtheiten traten nur mit den externen Programmen/Packern/Entpackern auf, da bleibt nur der Trost, dass zumindestens das Auspacken der Archive keine respektive geringe Probleme verursacht, wenn die Dateien im Archiv der 8+3-Konvention entsprechen. Diese Probleme kann man aber nicht ARC-View anlasten.

So kann man jedem User mit MagiC, der lange Dateinamen nutzt und mit Archiven arbeitet, diese Shell nur wärmstens empfehlen. Gerade der problemlose Umgang mit Archiven, die lange Dateinamen enthalten bzw. deren Dateinamen über die 8+3-Notation hinausgehen, prädestiniert die ARC-Shell für den Einsatz unter MiNT.

Es bleibt zu hoffen, dass es dem Autor gelingt, die Shell MiNT-kompatibel zu machen und andersherum, dass es die MiNT-Programmierer motiviert, aktuelle Packer für den Atari zu portieren.


Autor

Philipp Donzé
Rue de l'industrie 7
1020 Renens
Schweiz

Philipp Donzé (PhilippDonze@gmx.ch)
Homepage: http://philippdonze.atari.org/, http://home.tiscalinet.ch/donze/



Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Bezug

Bezug von ARC-View

Mailbox:
MAUS Münster 2
Kürzel: MS2
Telefon: 0251-77262

Benutzerinfos:
Die Ur-MAUS hat jetzt eine Schwester: ONLINE seit 5. April 1992.
2400 (V.22bis), 9600 (V.32), 14400 (V.32bis) 28800 (V.34bis) bps.
ISDN (X.75) unter Rufnummer +49-251-77262.
Gastdownload erlaubt. In den MausTauschzeiten sind Downloads nicht erwünscht!
Diese Tauschzeiten sind: 06:00-09:00, 12:00-14:00, 19:00-22:00 Uhr.
Zu diesen Zeiten ist die MAUS MS2 zudem für Gäste gesperrt.


Fileliste der Maus @ MS2 vom 15.08.01:

Öffentlicher Programmteil

NICHT nach den Nummern suchen lassen, sondern nach dem Namen!

1654  ST TOS   ARCV06.ZIP     42928   23:48    0     0.00  08.08.01
   Packer, Freeware
    ARC-View 0.6 von Philipp Donze(PhilippDonze@gmx.ch) ist die Packer-Shell
    für ARC, ARJ, ZOO, TAR, ZIP und LZH unter MagiC(läuft z.Zt.leider nicht
    unter MiNT). Archive werden ala Desktop-Fenster angezeigt. AV-fähig. Deskop
    (z.B. Jinnee) wird benötigt.
    Von Thomas Kerkloh @ MS2

Bezug aus dem Internet

Bezug von diversen Packern/Entpackern

Mailbox
MAUS Münster 2
Kürzel: MS2
Telefon: 0251-77262

Benutzerinfos:
Die Ur-MAUS hat jetzt eine Schwester: ONLINE seit 5. April 1992.
2400 (V.22bis), 9600 (V.32), 14400 (V.32bis) 28800 (V.34bis) bps.
ISDN (X.75) unter Rufnummer +49-251-77262.
Gastdownload erlaubt. In den MausTauschzeiten sind Downloads nicht erwünscht!
Diese Tauschzeiten sind: 06:00-09:00, 12:00-14:00, 19:00-22:00 Uhr.
Zu diesen Zeiten ist die MAUS MS2 zudem für Gäste gesperrt.



Fileliste der Maus @ MS2 vom 21.03.99:

Öffentlicher Programmteil

NICHT nach den Nummern suchen lassen, sondern nach dem Namen!

Nr.  System   Filename       Bytes   Dauer  Abruf   DpM   Datum

21   ST TOS   ARC6.TOS       33077   00:00  175     1.65  06.07.90
   Packer, Shareware
    Aktuelle Version von ARC als selbstextrahierendes LZH-Archiv (einfach
    vom Desktop starten, dann wird er ausgepackt)
    Von Julian Reschke @ MS


164  ST TOS   ZOO21_4.TOS    66609   00:00  148     1.79  11.06.92
   Packer, Freeware
    Zoo 2.1 - Rel4 der Portierung von G. Steffens.
    Startet jetzt schneller, kann Files im Root-Dir auspacken,
    keine Probleme mehr unter MiNT oder bei riesigen Archiven.
    (Selbstentpackendes LZH-Archiv)
    Von Bene Ostendarp @ MS2


1388 ST TOS   ARJ_997A.TOS  233684   00:00   13     0.46  22.11.96
   Packer, Shareware
    (v9.97A, rev. 3.101) ARJ by the TOS crew.
    100% compatible with R.Jung's ARJ.EXE for IBM
    ARJ: faster, more packing modes,  better packing ratios, multiple volume
    UNARJ: password protection, diskette user  support, extra verbose,
    on-line manual usw.(c) '93-94-95-96 Hans Wessels & Ger Hobbelt
    Von Thomas Kerkloh @ MS2

1657 ST TOS   LHARC313.ZIP  139019   00:39    0     0.00  18.08.01
   Packer, Freeware
    LHarc junior v3.13 [03.10.97]: Der bekannte (Ent-) Packer.
    Neu:Diverse Bugfixes (u.a. Schalter -n und -y funktionieren wieder,
    Probleme mit case-sensitiven Filesystemen und Links beseitigt).
    Archiv enthaelt:
    TTP-Version inkl. Doku (GER und ENG), MAKE_SFX, UPL-Text und Icons.
    Von Thomas Kerkloh @ MS2

1655  ST TOS   TAR112.ZIP     84762   23:50    0     0.00  08.08.01
   Packer, GNU-Lizenz
    tar (GNU tar) 1.12 von John Gilmore und Jay Fenlason von 1997.
    Nicht ganz so einfach, magere Doku. Läuft aber mit ARC-View.
    Von Thomas Kerkloh @ MS2

1656  ST TOS   ZIP23_02.ZIP  148111   23:51    0     0.00  08.08.01
   Packer, Freeware
    Zip 2.3 und Unzip 5.40R2 von Philipp Donze (PhilippDonze@gmx.ch) portiert.
    Basieren auf den Info-ZIP-Sourcen, div. Bugs wurden beseitigt(leider
    nicht alle).
    Kommt mit langen Datenamen zurecht. Passt prima zu ARC-View...
    Von Thomas Kerkloh @ MS2

Internet

Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ARCView
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

Archivarten

Es folgen hier einige Infos zu den einzelnen Archiv-Arten, mehr Informationen inkl. Formatbeschreibungen findet man hier:

http://www.geocities.com/SiliconValley/Horizon/3433/FFIDX.HTM bzw. http://www.wotsit.org/

ARC

Müsste eines der ersten Archivformate sein, wahrscheinlich abgekürzt aus dem Namen ARChive.

Auch auf dem Atari kaum noch gebräuchlich.

ARJ

Stammt auch vom Intel-PC, beherrscht als Einziger Packer/Entpacker auf dem Atari MultiVolumes.

Wird auch auf dem Atari kaum noch eingesetzt.

HQX

Stammt vom Apple Macintosh, entspricht dem UUE auf anderen Plattformen. Es wird dort ein 8-Bit-File in ein 6-Bit-File gewandelt. Einsatzzweck ist der Datentransfer z.B. über das MausNet und Internet. Entpackt wird u.a. dort mit BinHex.

Es gibt einen Entpacker/Decodierer für HQX auf dem Atari, allerdings klappt das Decodieren nicht immer.

SIT

Stammt vom Apple Macintosh, StuffIT ist ein Packer/Entpacker von Aladdin, der als Shareware/Freeware und als kommerzielle Lösung existiert.

Die Portierung eines Atari-SIT-Entpackers hat leider diverse Fehler.

BIN

Stammt vom Apple Macintosh.

LZH

Auch Lharc oder LZH oder LHA oder LZS, 4 Begriffe für einen Packer. Die Sourcen zu Lharc sind frei, also könnte man doch auch noch einbauen, das übergebene Archive/Dateien Dateinamen auch Leerzeichen haben dürfen...

Lange Dateinamen funktionieren allerdings!

Dieses Format trifft man noch häufiger auf dem Atari.

LZS

Identisch mit dem Lharc LZH oder LSZ oder LHA.

LHA

Identisch mit dem Lharc LZH oder LZS.

RAR

Ein aus Russland stammender Packer mit sehr guter Packrate; im Bereich der Windows-Systeme wird dieser Packer gerne eingesetzt. Die Vorgehensweise ist ähnlich dem TAR: zuerst werden die Daten aneinandergereiht und danach komprimiert. Neben verschiedenen Packstufen beherrscht er auch selbstextrahierende Archive und sogenannte MultiVolumes, die sogar in der Größe sogar noch einstellbar sind. MultiVolumes sind recht nützlich, wenn man einen großen Datenbestand über kleine Medien transferieren möchte, z.B. seine 120 MB große Textdateiensammlung per Diskette zu einem anderen Rechner. RAR kann aus diesen ca. 40 MB gepackten Daten z.B. jeweils 40 Einzelarchive á 1 MB machen, die per Diskette zum anderen Rechner geschafft werden können.

Die aktuellen Versionen können leider nicht mehr am Atari entpackt werden, sind dort aber auch selten anzutreffen.

TAR

Wie schon beschrieben, ist TAR eigentlich ein Archivierer, der Daten zusammenfasst. Da man diese Dateien gut packen/komprimieren kann, lässt man dies im Regelfall vom ZIP erledigen. Dieses Format ist eigentlich in der UNIX-Welt für die Archivierung auf Bändern gedacht (Tape ARchive))und hat sich seit dem Hype auf Linux weiter verbreitet, da es die Besitzer- und Zugriffs-Rechte speichert. Unter anderem werden auch Pakete aus dem MiNT-Bereich als GNU-gezippte TAR-Archive verteilt. Die Extension *.TGZ weist auf diese Packart hin.

TGZ

TAR-Archive mit GNU-ZIP gepackt, siehe auch TAR. GNU-ZIP kann nur eine Datei packen (deshalb wird vorher mit TAR alles zu einer Datei zusammengefasst) und ist nicht mit dem normalen ZIP kompatibel.

Wird überwiegend unter Linux und MiNT eingesetzt.

ZIP

Die Portierung von Philipp Donzé kann mit langen Dateinamen umgehen, auch sonst sind einige Unschönheiten beseitigt worden. ZIP ist eigentlich das Packformat auf dem Atari und Intel-PC.

ZOO

Gehört ebenfalls zu der Klasse Uraltformat, ist noch hin und wieder auf dem Atari anzutreffen. Spielt im Intel-PC-Bereich aber eigentlich keine Rolle mehr.

UUE

Was HQX für den Apple Macintosh ist, ist UUE für den Rest der Welt. 8Bit-Dateien lassen/ließen sich nicht per Telefon übertragen, also wurden sie ins 6-Bit-Format gewandelt und als Text verschickt. Der wohl prominenteste UUDDecoder ist CAT, ESS UUEncode/UUDecode 4.0 kann auch Dateien ins UU-Format wandeln.

Thomas Kerkloh


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - ADAT 16
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

11.09.01

Adat 16

vom 6.-9.12.2001 in Willebadessen

Von Thomas Kerkloh

Vom 6.-9.12.2001 findet in Willebadessen das ADAT 16 statt.

ADAT 16 = Allgemeines DTP-AnwenderInnen-Treffen

Wann: 06.-09.12.2001 (Do-So)
Wo: Willebadessen bei Paderborn
Zimmer: Es gibt Zimmer mit garantierter Dusche in der neuen Ackerscheune, die etwas teurer sind (100 DM Vollpension pro Tag) und günstigere Zimmer im Haupthaus teils mit Zimmerdusche, teils mit Etagendusche (80 DM Vollpension pro Tag).
Essen: Alles möglich, Normalos, Vegetarier und Co. kommen nach Voranmeldung alle auf ihre Kosten.
Programm: Gibt es noch nicht, aber DTPler und Co. kommen voll auf ihre Kosten. Möchte jemand etwas Interessantes zu dem Thema DTP vorbringen oder hat Ideen für Workshops und dergleichen, so kann er sich damit an Ulf Dunkel (dunkel@calamus.net) wenden.
Rechner: Sollte(macht schon Sinn)/Kann(muss aber nicht) jeder selber mitbringen.

Anmeldungen und mehr Informationen über Ulf Dunkel (dunkel@calamus.net).

Genauere Informationen, was ein ADAT ist, findet man im Online-Archiv der ATOS. Ein Bericht über das ADAT 14 ist dort ebenfalls zu finden.

Thomas Kerkloh


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende

15.06.2001

ANSI C

Ein Programmierkurs in mehreren Teilen - Teil I

Von Michael Bernstein

Herzlich willkommen zum C-Kurs für die ATOS!

Das Ziel des Kurses ist lediglich ANSI C. Die Programmierung von GEM oder spezieller Atari-Hardware wird hier nicht behandelt. Dies würde zum einen den Rahmen des Kurses sprengen, zum anderen ist für die GEM-Programmierung schon ein gewisses Maß an C-Kenntnissen erforderlich.

Warum nun gerade C, wo doch mittlerweile Java und C++ in aller Munde sind?

Im Gegensatz zu Java existiert C auch für Atari-Computer. C setzt weniger Anforderungen an die Leistung der Hardware als die weiterführenden Konzepte von C++ und Java voraus. Auch embedded systems (die berühmte Kaffeemaschine) werden nicht mit Java programmiert, um nicht einen 500-MHz-Pentium reinzustecken. Da sitzen durchaus noch 8-Bit-Mikrocontroller drin.

Die für Java vielgepriesene Portierbarkeit spielt für solche Geräte sowieso keine Rolle - wer möchte schon die Steuerung für seine Kaffeemaschine als Betriebssystem auf seinem PC laufen lassen - Kaffee kochen kann er sowieso nicht.

Außerdem ist der Zugriff auf die spezielle Hardware nötig. Deshalb kommt man mit Java allein nicht weiter. Auch hierfür eignet sich C deutlich besser.

C ist eine universelle Programmiersprache, die nicht an spezielle Aufgabenstellungen gebunden ist. C-Compiler erzeugen recht schnelle Programme und auch hardwarenahes Programmieren ist möglich, wie z.B. die in C geschriebenen Betriebssysteme (UNIX, TOS) zeigen.

ANSI C ist normiert, und bei Beachtung einiger Regeln lassen sich die Programme leicht auf andere Plattformen portieren. Leider hat diese schöne Eigenschaft ein paar Haken: Die Programmierung einer grafischen Oberfläche oder der Zugriff auf Hardware (auch schon serielle Schnittstellen!) gehören nicht zur ANSI-Norm. Damit lassen sich nur Programme leicht portieren, die über die Tastatur bedient werden. Eine gute Aufteilung der Programme kann aber auch solche Portierungen erleichtern.

Der Sprachumfang von C ist Bestandteil des Sprachumfangs von C++. Damit könnte ein C++-Kurs auf einen C-Kurs aufbauen. Es gibt aber auch die Meinung, dass Kenntnisse in C es erschweren, objektorientiertes Programmieren zu erlernen.

C ist eine kleine Sprache, die nur aus relativ wenig Sprachelementen besteht und damit leicht zu lernen ist. Die Anwendung von Zeigern ist aber dennoch eine kleine Hürde für einen Programmieranfänger. ANSI C enthält im Sprachumfang noch eine umfangreiche Sammlung von Bibliotheken.

Der Kurs

Der Kurs ist so aufgebaut, dass er auch für Anfänger geeignet ist, die noch keine Programmiersprache beherrschen. Das Beispiel, das im Rahmen des Kurses entwickelt wird, dient dazu, den Cookie-Jar auszulesen und die Informationen anzuzeigen. Damit ist auch der Bezug zu Atari-Computern hergestellt ;-). Das Beispiel ist aber auch nicht auf andere Computer portierbar. Umfangreichere Projekte würden nur den Blick auf das Wesentliche verdecken. Es ist jedem freigestellt, die Beispiele entsprechend zu modifizieren und weiterzuentwickeln, um eigene Vorstellungen zu verwirklichen.

Experten mögen mir verzeihen, dass ich an manchen Stellen zuerst einige Dinge vereinfache und eher lockere Erklärungen bevorzuge. Aber der Kurs wird in der Freizeit von den Teilnehmern verfolgt und Aktivitäten in der Freizeit sollten immer auch Freude machen.

Ich werde keine Übungsaufgaben stellen, da ich sehr ungern andere Menschen bewerten möchte. Auch ist es in einem Kurs in einem Magazin sehr unpraktisch, Lösungen der Teilnehmer zu besprechen. Es könnten nur Musterlösungen vorgestellt werden. Dafür werden Sie häufiger aufgefordert, einige Dinge selbst zu probieren. Dadurch gewinnt man Anschauung und Anschauung erleichtert das Verständnis. Wie man nun das technische Wissen über C in eine Lösung für ein konkretes Problem umsetzt, lernt man nur durch Übung. Dafür gibt es keine Richtlinien und das macht die Softwareentwicklung auch zu einem kreativen und anspruchsvollen Vorgang. Zu einem guten Programmierer gehört ein entsprechendes Wissen und Erfahrung, also auch Übung.

Geschichtliches

C wurde in den 70er Jahren auf und zusammen mit dem Betriebssystem UNIX entwickelt. Viele der wichtigen Ideen in C stammen von der Sprache BCPL, die Martin Richards entwickelt hat. BCPL beeinflusste C indirekt durch die Sprache B, die Ken Thompson 1970 für das erste UNIX-System auf der DEC PDP-7 implementiert hat.

Wer mehr über die Hintergründe der Entwicklung von C nachlesen möchte, sollte sich einmal die folgende Web-Page anschauen :-)
http://www2.informatik.uni-halle.de/lehre/pascal/sprache/humor_ix.html

Voraussetzungen

Es stellt sich zuerst die Frage, welches System geeignet ist. Bei den von mir genannten Compilern ist das jeder Atari, der mindestens 1 MB bzw. 2 MB RAM hat, je nach verwendetem Compiler. Alternativ kann auch ein ATARI-Emulator benutzt werden.

Welche Compiler gibt es?

Zum einen Turbo C bzw. dessen Nachfolger Pure C. Leider gibt es diesen Compiler nur noch gebraucht. Er lässt sich allerdings auch sehr gut auf einem ST mit 1 MB RAM einsetzen. Bevor ich mir eine Festplatte gekauft hatte, war er bei mir auf einer komprimierenden RAM-Disk installiert. So waren während der Arbeit keine weiteren Zugriffe auf Disketten erforderlich.

Sozobon C

Mit diesem Compiler habe ich noch keine Erfahrungen. Ich werde die Beispiele aber auch mit diesem Compiler testen, weil er als Freeware verfügbar ist.

GCC

Für diejenigen mit dem besonderen Geschmack (oder nur masochistisch veranlagte?). Dieser Compiler arbeitet sehr gründlich. Oder sollte ich besser sagen, er ist nicht der schnellste? Er erzeugt aber dennoch guten Code. Auf einem normalen ST ist er mir allerdings zu langsam. Die aktuelle Version für Ataris ist die 2.95.2. Der Compiler wurde nur unter MiNT getestet, sollte aber auch unter MagiC laufen. Er benötigt ein Dateisystem mit langen Dateinamen. Der C-Compiler benötigt 4 MB freies RAM, der C++-Compiler 8 MB freies RAM. Auf der Festplatte benötigt der Compiler 10 MB; die MiNTLib 5 MB und die binutils nochmals 5 MB. Mit weiteren Utilities wie make, fileutils, sh-utils werden ca. 30 MB benötigt. Es empfiehlt sich deshalb ein Atari ab TT aufwärts, Falcon mit Beschleuniger, ST mit PAK/FRAK, Hades, Milan. Da es recht leicht möglich ist, denn gcc als Crosscompiler zu bauen, lässt sich auch ein aktueller PC verwenden, um für den Atari Programme zu übersetzen. Für diejenigen mit kleineren Ataris, die den gcc testen wollen, habe ich die Version 2.5.8 zur Verfügung gestellt. Diese Version ist zwar älter, sie gibt sich aber noch mit 2 MB RAM zufrieden, wenn man kleinere Programme übersetzt. Ich habe ihn auf einem ST mit 2,5 MB und einer grafischen Shell getestet. Mein Archiv hat zusätzlich den Vorteil, dass es komplett ist, also auch den C++-Compiler enthält. Dies war leider auch nicht bei jeder Quelle gegeben. Zum Teil fehlten Teile des C++-Compilers, z.B. die Libs oder der Compiler selbst. Dieser Compiler kann nur mit einer Festplatte sinnvoll betrieben werden.

Mit weiteren Systemen habe ich keine Erfahrung. Die Beispiele werde ich mit jedem der oben genannten Compiler testen.

Wo gibt es die Entwicklungssysteme?

Eine Beschreibung des MausNet findet sich im WWW unter http://www.hoffart.de/maus.html

Was muss gesaugt werden?

Wird eine andere Version benutzt, können die Namen und auch die Aufteilung auf Archive abweichen. Diese Aufstellung bezieht sich auf die Version 2.0 des Sozobon bzw. 2.5.8 des GCC in den oben genannten Quellen. Auch wenn ich unten zu Archiven schreibe, dass sie nicht unbedingt nötig sind, sollte man sich die Entwicklungsumgebungen komplett holen. Es könnten Teile später benötigt werden. Es ist aber nicht erforderlich, diese Teile sofort zu installieren. Dazu gehört z.B. auch die Doku, die natürlich für den Kurs nicht unbedingt erforderlich, aber manchmal sehr hilfreich ist.

Sozobon

Ein Stern in der Spalte n kennzeichnet die unbedingt notwendigen Archive.

Archivname n Inhalt
fplib20.lzh   Bibliothek für FPU-Nutzung
jas2x9.lzh   Assembler
mntsox46.zoo   MiNT-Lib, Alternative zur Standardlib
scbin20.lzh * Compiler, Version 2.0
scdoc20.lzh   Dokumentation des Systems
scsrc20.lzh   Quellcode des Systems
sozbin15.zoo   Compiler, Version 1.5
sozc6844.lzh   Portierung des c68 Compilers
sozlib15.zip * Bibliotheken
sozuti09.zip * enthält weitere Hilfsprogramme, auch make
szadb21b.zoo   ein Assembler-Debugger für Sozobon C

GCC

Beim GCC muss man entscheiden, welche Bibliotheken man installiert. Es gibt zum einen die Standard-Lib von GNU, die für diesen Kurs völlig ausreichend ist. Zum anderen gibt es noch die MiNT-Libs, die bei installiertem MiNT auch dessen Möglichkeiten ausnutzen können. Die mit den MiNT-Libs gelinkten Programme laufen aber auch problemlos unter TOS. Probleme gibt es nur, wenn man die speziellen Möglichkeiten von MiNT auch ausnutzt. Wenn z.B. Multitasking nötig ist, ...

Ein Stern in der Spalte n kennzeichnet die unbedingt notwendigen Archive.

Archivname n Inhalt
gcc258ba.zoo * Compiler, ... Teil 1
gcc258bb.zoo * Compiler, ... Teil 2
0readme   eine Kurzinstallationsanleitung
asmtrans.ttp    
gccman.zoo   die Hilfe
genmagic.ttp    
make.ttp * nötig, für Projekte mit mehreren Dateien
mkptypes   Tool, generiert Prototypen aus C Dateien
utlbin38.zoo * Tools, Linker, ...
gemolb30.zoo   GEM-Lib V3.0
includ97.zoo * Include-Dateien
libolb97.zoo * Standard-Bibliotheken
gemlib30.zoo   Quellen der GEM-Lib V3.0
libsrc97.zoo   Quellen der Standard-Bibliothek
pmllib21.zoo   Quellen einer portablen Mathematikbibliothek
pmlolb21.zoo   eine portable Mathematikbibliothek
crsdoc16.zoo   Doku einer Lib für Textbildschirmsteuerung
crslib16.zoo   eine Lib für Textbildschirmsteuerung
crsolb16.zoo   eine Lib für Textbildschirmsteuerung
crssrc16.zoo   Quellen einer Lib für Textbildschirmsteuerung
geminc31.tgz   Includedateien der GEM-Lib V3.1
gemolb31.tgz   GEM-Lib V3.1
gempc31.tgz   GEM-Lib V3.1
gemsrc31.tgz   Quellen der GEM-Lib V3.1
g++20o22.zoo   Bibliothek für C++
g++lib22.zoo   Bibliothek für C++
g++olb22.zoo   Bibliothek für C++
g++inc22.zoo   Includedatein für C++ Bibliothek
g+libt02.zoo   Bibliothek für C++
gdbm171.zoo   Debugger
gprof08b.zoo   Profiler
mntlib46.tgz   Quellen einer alternativen Standardbibliothek
mntolb46.tgz   eine alternative Standardlib
mlib46d.tgz   eine alternative Standardbibliothek
mntc6846.zoo   eine alternative Standardbibliothek
mntinc46.tgz   Includedatei einer alternativen Standardbibliothek
mntsox46.zoo   eine alternative Standardbibliothek
pcosbind.zoo    

Was fehlt noch?

Um die Archive zu installieren, wird ein Packer benötigt. Verwendet wurden bei den Archiven zoo (Endung: .zoo), lharc bzw. lzhshell (.lzh), pkzip auf PC bzw. stzip auf Atari (.zip) und tar und gzip (.tgz).

Wer Sozobon oder GCC benutzt, benötigt noch einen Editor. Hier gibt es genug Auswahl. Schließlich ist das Schreiben eines Editors eines der drei Dinge, die ein Mann in seinem Leben machen sollte. Es gibt z.B. QED, Everest, 7up, Luna, ...

Für Sozobon und GCC ist ein Kommandozeileninterpreter (Shell) ganz praktisch. Es gibt z.B. die Mupfel, die zu Gemini gehört, Gulam oder unter MiNT auch die bash.

Sowohl für Sozobon als auch für den GCC gibt es auch noch Shells, die eine IDE (GEM-Oberfläche für die Entwicklung) zur Verfügung stellen. Dies wären für Sozobon CDESK als Shareware. Für den GCC gibt es die Gnushell oder Agnus, beide ebenfalls Shareware.

Wer ein älteres TOS (<1.06) einsetzt, das noch keinen Cookie-Jar anlegt, benötigt ein entsprechendes Programm. Ich habe mein Programm, das frei nach einem Artikel im Profibuch entstanden ist, auf meine Homepage (http://home.t-online.de/home/Michael_Bernstein/download/index.htm) gelegt.

Links auf einige der Tools, insbesondere die Packer und Editoren, finden sich z.B. auf der Downloadseite meiner Homepage (http://home.t-online.de/home/Michael_Bernstein/download/index.htm).

Literatur

Ich selbst habe kaum Literatur benutzt. Anfangs waren es das Skript zu einer Vorlesung und anschließend die Online-Hilfe. Deshalb kann ich keine Bewertung anbieten. Hier folgt dennoch eine kurze Liste:

  • Kernighan/Ritchie: Programmieren in C, Carl Hanser Verlag
    Dies ist der Klassiker schlechthin, schließlich sind die Autoren die Entwickler von C. Allerdings ursprünglich in einer Variante als K&R C, die meiner Meinung nach durch einen sehr laschen Syntax zu viele Fehlermöglichkeiten offenlässt.
  • C-FAQ aus dem Internet, z.B. unter http://www.hrz.uni-giessen.de/faq/archiv/c-faq.faq/

Im nächsten Teil werden die unterschiedlichen Entwicklungsumgebungen in Betrieb genommen (Turbo-C/Pure-C, Sozobon, GCC).

Michael Bernstein


Anfang zurück vorwärts Ende Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil II

Compiler

Als erste Übung werden wir die Entwicklungsumgebungen in Betrieb nehmen und konfigurieren. Dies unterscheidet sich sehr stark, je nach verwendetem Compiler und verwendeter Shell. Sozobon und GCC ähneln sich etwas, da beide Kommandozeilenprogramme sind, und die unbedingt notwendigen Parameter identisch sind.

Die Konfiguration für Turbo-C/Pure-C (TC/PC) bzw. weitere Kommandozeilenparameter für Sozobon und GCC werden im Anhang nochmals detaillierter ausgeführt. Hier werden nur die unbedingt notwendigen Einstellungen besprochen.

Als Beispielprogramm ist folgendes Programm einzugeben und unter dem Namen first.c zu speichern. Es ist der Klassiker der C-Programmierung; sehr viele C-Kurse bzw. Bücher beginnen mit diesem Programm.

Achtung! Nach der letzten Zeile ist noch einmal ein Zeilenumbruch (RETURN-Taste drücken) erforderlich, damit der Sozobon-Compiler das Programm akzeptiert.

first.c

#include <stdio.h>

int main(void)
{
   printf("Hallo Welt!");
   return 0;
}

TC/PC

Der Inhalt der Disketten ist in ein beliebiges Verzeichnis zu kopieren, z.B. c:\tc bzw. c:\pc. In diesem Verzeichnis sollten unter anderem die 2 Verzeichnisse include und lib zu finden sein; außerdem tc.prg, tc.cfg bzw. pc.prg, pc.cfg und default.prj. Im Verzeichnis include befinden sich Dateien mit der Extension .h, im Verzeichnis lib befinden sich Dateien mit der Extension .lib sowie die Datei tcstart.o bzw. pcstart.o. Die Bedeutung dieser Dateien wird später erklärt.

Dies sind auch die Dateien, die für ein Minimalsystem auf Diskette unbedingt erforderlich sind.

Konfiguration

Als nächstes muss das System konfiguriert werden. Die hier abgebildeten Dialoge gehören zu TC. Die entsprechenden Dialoge von PC unterscheiden sich nur minimal, die wesentlichen Einträge sind auch unter PC vorhanden.

Unter Options im Menüpunkt Compiler erscheint folgender Dialog:

[Bild]

Hier muss das include directory auf den entsprechenden Pfad gesetzt werden. Das wäre im obigen Beispiel: c:\tc\include

Unter Options im Menüpunkt Linker erscheint ein Dialog für die Linkeroptionen:

[Bild]

Hier muss das library directory auf den entsprechenden Pfad gesetzt werden. Das wäre im obigen Beispiel: c:\tc\lib

Anschließend bitte nicht das Abspeichern vergessen; dazu unter Options den Menüpunkt save TC.CFG aufrufen.

Jetzt kann unter File mit dem Menüpunkt Open C mit dem Fileselektor eine neue Datei angelegt und editiert werden. Unter Project mit dem Menüpunkt Run kann diese Datei übersetzt und sofort ausgeführt werden.

[Bild]

Jetzt sollte normalerweise der Compiler das Programm übersetzen, der Linker diese Datei mit den dazu benötigten Bibliotheken zusammenbinden und anschließend das Programm ausgeführt werden. TC/PC öffnet ein neues Fenster, in dem jetzt nur eine Meldung des Linkers erscheint. Sollten auch Fehlermeldungen des Compilers erscheinen, ist das Programm fehlerhaft abgetippt worden und sollte auf Tippfehler untersucht werden. Meldungen über doppelt definierte Symbole, die der Linker ausgibt, können hier ignoriert werden.

Da sowohl Compiler als auch Linker vor Ausgabe der Meldungen eine Überschrift wie ******* Compiler messages: ausgeben, können die Meldungen eindeutig zugeordnet werden. Mit der Taste Esc kann der Bildschirm auf die Ausgabe des Programms umgeschaltet werden. Diese Umschaltung funktioniert allerdings nur in den Standardauflösungen und nicht mit Grafikkarten. Auch Shells oder Multitasking-Betriebssysteme mit einem eigenen Konsolenfenster könnten die Ausgabe beeinflussen.

Wenn es Probleme gibt, die Ausgabe des Programms zu lesen, kann das Programm wie folgt erweitert werden. Es wartet damit vor der Beendigung darauf, dass der Benutzer die Returntaste drückt.

Wartendes first.c

#include <stdio.h>

int main(void)
{
   printf("Hallo Welt!");
   getchar();
   return 0;
}

Sozobon

Der Inhalt der Archive ist in ein beliebiges Verzeichnis zu entpacken, z.B. c:\sozobon. Die Dateien des Archivs scbin20.lzh (cc.ttp, ...) gehören in das Unterverzeichnis bin. Hier sind die ausführbaren Programme zu finden. Achtung: Wird die Pfadangabe des Archivs übernommen, dann wird ein Verzeichnis scbin20 angelegt, das die Programme enthält. Dieses Verzeichnis kann in bin umbenannt, oder der Inhalt kopiert werden.

Das Archiv sozlib15.zip enthält die Bibliotheken. Nach dem Auspacken findet man ein Verzeichnis sozdistr vor. Dessen Inhalt ist in das gewünschte Verzeichnis, hier c:\sozobon, für Sozobon zu kopieren.

Das Gleiche ist mit sozuti09.zip durchzuführen. Hier sind noch einige Hilfsprogramme enthalten. Das darin befindliche make.ttp wird benötigt, wenn das Projekt auf mehrere Dateien aufgeteilt wird.

Als nächstes muss das Environment gesetzt werden, damit der Compiler die Includedateien, den Linker, und der Linker die Bibliotheken findet. Folgende Variablen sind zu setzen:

LIB
INCLUDE
PATH
TEMP

Für die Shell Guläm ist unten ein Skript vorgestellt, das dies erledigt. Die dort aufgeführten Pfade müssen noch auf das Verzeichnis von Sozobon auf dem eigenen System angepasst werden. Die Pfade für das Include- und das Libverzeichnis gelten so für den Sozobon-Compiler auf der Seidel SDK-CD bzw. den oben angegebenen Quellen. Sollte sich die verwendete Distribution hier unterscheiden, so sind die Pfade entsprechend anzupassen.

Im Includeverzeichnis muss eine größere Zahl von Dateien mit der Extension .h zu finden sein, darunter auch stdio.h. In dem Libverzeichnis sind unter anderen crt0.o und eine Anzahl Dateien mit der Extension .a zu finden.

Es ist auch möglich, die Verzeichnisnamen aus den Archiven direkt zu benutzen und das Environment entsprechend anzupassen.

Das Programm wird mit folgender Kommandozeile übersetzt:

cc -o first.tos first.c

Es sollte keine weitere Meldung auf dem Monitor erscheinen. Anschließend sollte in dem Verzeichnis, in dem first.c steht, das übersetzte Programm first.tos stehen.

Wenn von Guläm die Fehlermeldung kommt, dass der Befehl cc nicht gefunden wurde, stimmt entweder der Wert der Environmentvariable PATH nicht oder es wurde versäumt, mit dem Befehl rehash Guläm anzuweisen, die Verzeichnisse der PATH-Variable nach ausführbaren Programmen zu scannen.

Wenn vom Compiler die Fehlermeldung erscheint, dass die Includedatei stdio.h nicht gefunden wurde, stimmt der Wert für INCLUDE nicht.

Genauso ist bei der Fehlermeldung des Linkers, dass er crt0.o oder eine Lib nicht gefunden habe, die Variable LIB zu prüfen.

sozobon.g

setenv LIB c:\sozobon\lib\sozobonx\xdlibs
setenv INCLUDE c:\sozobon\include\xdlibs
setenv PATH c:\sozobon\bin
setenv TEMP c:\temp
rehash

GCC

Der Inhalt der Archive ist in ein beliebiges Verzeichnis zu entpacken, z.B. c:\gnu. Der Inhalt der Archive gcc258ba.zoo und gcc258bb.zoo wird in das Unterverzeichnis bin entpackt.

Der Inhalt des Archivs includ97.zoo gehört in das Verzeichnis include. Die Dateien, die in diesem Archiv im Verzeichnis sys sind, gehören natürlich nach include\sys.

Der Inhalt des Archivs libolb97.zoo kommt in das Verzeichnis lib. Als nächstes muss das Environment gesetzt werden, damit der Compiler die Includedateien, den Linker, und der Linker die Bibliotheken findet. Folgende Variablen sind zu setzen:

GNULIB
GNUINC
PATH
TEMP

Für den C++-Compiler müssen noch zusätzlich folgende Variablen gesetzt werden:

GXXINC und GXXLIB

Für die Shell Guläm ist unten ein Skript vorgestellt, dass dies erledigt. Die dort aufgeführten Pfade müssen noch auf das Verzeichnis von GCC auf dem eigenen System angepasst werden. In dem Includeverzeichnis muss eine größere Zahl von Dateien mit der Extension .h zu finden sein, darunter auch stdio.h. In dem Libverzeichnis sind unter anderen crt0.o und eine Anzahl Dateien mit der Extension .olb zu finden.

Das Programm wird mit folgender Kommandozeile übersetzt:

gcc -o first.tos first.c

Es sollte keine weitere Meldung auf dem Monitor erscheinen. Anschließend sollte in dem Verzeichnis, in dem first.c steht, das übersetzte Programm first.tos stehen.

Wenn von Guläm die Fehlermeldung kommt, dass der Befehl gcc nicht gefunden wurde, stimmt entweder der Wert der Environmentvariable PATH nicht, oder es wurde versäumt, mit dem Befehl rehash Guläm anzuweisen, die Verzeichnisse der PATH-Variable nach ausführbaren Programmen zu scannen.

Wenn vom Compiler die Fehlermeldung erscheint, dass die Includedatei stdio.h nicht gefunden wurde, stimmt der Wert für GNUINC nicht.

Genauso ist bei der Fehlermeldung des Linkers, dass er crt0.o oder gnu.olb nicht gefunden habe, die Variable GNULIB zu prüfen.

gnu.g

setenv GNULIB c:\gnu\lib
setenv GNUINC c:\gnu\include
setenv GCC_EXEC_PREFIX c:\gnu\bin\gcc-
setenv UNIXMODE .,/d
set path c:\gnu\bin,$path
setenv TEMP c:\temp
setenv TMPDIR c:\temp
rehash

Packer/Entpacker

Hier folgt eine Kurzanleitung zum Auspacken der Archive. Sie ersetzt nicht die Anleitung der entsprechenden Programme.

Die beiden Programme lzhshell und stzip sind jeweils GEM-Programme und deshalb selbsterklärend. Die Programme zoo, gzip und tar sollten als Kommandozeilenprogramme von einem Kommandozeileninterpreter (shell) gestartet werden. Es ist aber ausreichend, die Entpacker in das Verzeichnis des Archivs zu kopieren und nach einem Doppelklick die Parameter in den Dialog des Desktops einzugeben.

Die zoo-Archive werden mit folgender Befehlszeile ausgepackt:

zoo -extract <archiv.zoo>

Die tgz-Archive sind tar-Archive, die anschließend mit gzip gepackt wurden. Sie müssen also zuerst mit gzip ausgepackt, und anschließend die Dateien aus dem Archiv herausgeholt werden. Dazu werden zwei Programme gestartet. Zuerst wird das Archiv dekomprimiert:

gzip -d <archiv.tgz>

Anschließend werden die Dateien aus dem Archiv herausgeholt:

tar xf <archiv.tar>

Anmerkung: bei neueren Versionen von tar lassen sich die beiden Schritte Entpacken und Auspacken auch in einem einzigen Schritt erledigen (nämlich dann, wenn tar die Option z versteht):

tar xzf <archiv.tgz>

Struktur eines C-Programms

Schauen wir uns mal das (leicht erweiterte) Beispiel an. Diese Datei besteht aus verschiedenen Bereichen. Bei den Zeilen mit einem # in der ersten Spalte handelt es sich um Anweisungen für den Präprozessor. Darunter befinden sich eine Deklaration einer Variablen und im Anschluss daran eine Funktion. Ein gültiges C-Programm muss mindestens eine Funktion mit dem Namen main enthalten! Die Reihenfolge, in der Funktionen, Deklarationen und Präprozessoranweisungen erscheinen, ist beliebig, wichtig ist nur der Gültigkeitsbereich. Sie können auch mehrfach vorhanden sein oder entfallen, wenn sie nicht benötigt werden. Unser Beispiel first.c, das dazu diente, die Compiler in Betrieb zu nehmen, hatte keine Deklarationen. Um sich schneller in fremde Programme einzuarbeiten, wird meist die unten aufgeführte Reihenfolge bevorzugt.

#include <stdio.h>              |- Präprozessoranweisungen
                                 /
                                 \ 
 int i;                          |- Deklarationen von Datentypen
                                 |  und Variablen
                                 /
 int main(void)                  \ 
 {                               |
    int i; /* als Beispiel */    |
                                 |- Funktionen
    printf("Hallo Welt!");       |
    return 0;                    |
 }                               /

Der in /* und */ eingeschlossene Text ist ein Kommentar, der vom Compiler überlesen wird. Kommentare dienen nur dazu, einem Programmierer Hinweise o.ä. zu geben. Sie können damit zum Verständnis von Programmen beitragen oder aber Teile des Codes von der Übersetzung ausnehmen.

Einrückungen o.ä. sind nicht relevant und dienen nur dazu, dass der Programmierer optisch die Struktur des Programms leichter erfasst. Das obige Programm ließe sich deshalb z.B. auch wie folgt schreiben:

#include <stdio.h>
int i;
int main(void) {int i;printf(Hallo Welt!);return 0;}

Durch Einfügen von Whitespaces, also Leerzeichen, Tabulatoren und Zeilenvorschübe, wird ein Programm lesbarer formatiert. Dazu wird jeder Block innerhalb geschweifter Klammern eingerückt. Eine gute Größe sind drei Leerzeichen, da hierbei noch nicht zu viel Platz einer Zeile verloren geht. Wenn auch die Monitore immer größer werden, so beschränkt man sich doch häufig auf maximal 80 Zeichen pro Zeile, damit auch bei einem Ausdruck die Zeile komplett auf das Papier passt und nicht vom Drucker umbrochen werden muss.

Die Funktion kann man auch weiter aufschlüsseln. Sie besteht aus einem Funktionskopf und einem Block in geschweiften Klammern. Dieser Block kann am Blockanfang Deklarationen und anschließend Anweisungen (bzw. Statements auf Neudeutsch) enthalten.

int main(void)                  |- Funktionskopf
{                               \ 
   int i;                       |- Deklarationen
                                /
   printf(Hallo Welt!);       \ 
   return 0;                    |- Anweisungen / Statements
}                               /

In den folgenden Kapiteln werden wir uns die einzelnen Teile genauer anschauen. Zuerst kommt etwas Theorie über Variablen und Datentypen. Anschließend können mit Anweisungen erste Übungen mit Programmen durchgeführt werden.

Nicht jedes der Beispiele wird ein komplettes Programm sein. Solche Beispiele werden dadurch gekennzeichnet, dass in ihnen 3 Punkte (...) als Hinweis auf fehlende Teile auftauchen. Solche Beispiele müssen Sie dann selbst in die Funktion main einbauen. Die benutzten Variablen sind auch zu deklarieren und auf sinnvolle Werte zu setzen, wenn noch keine Deklarationen vorhanden sind. Die letzte Zeile return 0; der Funktion main aus dem oben aufgeführten Beispiel ist immer mit zu übernehmen, wenn ein unvollständiges Beispiel in ein Programm übernommen wird. Andernfalls erhält man eine entsprechende Meldung des Compilers.

Ist in dem Beispiel eine Ausgabe (printf) enthalten, muss die Zeile include <stdio.h> auch mit übernommen werden. Ansonsten kann sie wegfallen.

Wenn die Beispiele übersetzt werden, können noch Warnungen des Compilers auftreten, weil z.B. Variablen nicht weiter benutzt werden. Sie können hier ignoriert werden, da eine Abhilfe das Programm nur unnötig vergrößern würde und das Wesentliche des Beispiels verschleiert. Bei der Entwicklung eigener Programme sollte man allerdings auch auf Warnungen ein Augenmerk richten, da es sich um versteckte Fehler handeln kann.

Variablen und Standarddatentypen

In C muss jede Variable durch die Angabe ihres Datentyps und Namens deklariert werden. Der Compiler kennt keine impliziten Variablen. Dies mag einem Basic-Programmierer vielleicht etwas umständlich erscheinen. Der Vorteil ist aber, dass gerade bei Verwendung von aussagekräftigen Namen ein Tippfehler zu einem ungültigen Namen führt und damit schon vom Compiler erkannt wird.

Variablen können außerhalb von Funktionen an jeder Beliebigen Stelle und am Anfang eines jeden Blocks (geschweifte Klammern) deklariert werden.

Gültige Namen

Ein in C gültiger Name besteht nur aus Buchstaben, Ziffern und dem Unterstrich '_', wobei zwischen Groß- und Kleinschreibung unterschieden wird.

Das erste Zeichen muss ein Buchstabe sein. Die deutschen Umlaute werden nicht zu den Buchstaben gezählt, hier sind nur 'a' - 'z' und 'A' - 'Z' erlaubt! Es werden mindestens die ersten 31 Zeichen für eine Unterscheidung der Namen herangezogen. Bei Namen, die aus anderen Bibliotheken stammen, können es aber weniger Zeichen sein; dann sind mindestens 6 Zeichen signifikant. Es muss hier auch nicht zwingend zwischen Groß- und Kleinschreibung unterschieden werden.

Einige Namen sind von der Sprache C schon reserviert und dürfen nicht für eigenen Namen benutzt werden. Diese reservierten Wörter sind im Kapitel reservierte Bezeichner aufgeführt.

Einige Beispiele für gültige Namen:

Name1 Test DiesIstEinSprechenderName dies_auch _Nur_intern Zeile_3
Dies_ist_ein_sehr_langer_Namen_mit_vielen_Buchstaben
Dies_ist_ein_sehr_langer_Namen_auch_mit_vielen_Buchstaben

Die beiden letzten Namen unterscheiden sich in den ersten 31 Zeichen nicht. Ein Compiler, der sich nur an das Minimum von mindestens 31 signifikanten Zeichen hält, kann diese beide Namen also nicht unterscheiden.

Einige Beispiele für unzulässige Namen:

3_Zeichen Häuser while

Es hat sich als Konvention eingebürgert, für Variablenbezeichner Kleinschreibung zu benutzen. Wenn ein Name aus mehreren Worten zusammengesetzt ist, werden die einzelnen Bestandteile durch einen Unterstrich voneinander abgetrennt:

dies_ist_ein_zusammengesetzter_name

Man sollte außerdem einen Namen wählen, der für die Verwendung der Variable aussagekräftig ist, also einen sprechenden Namen. Dies erleichtert es, die Verwendung der Variablen zu erkennen und damit ein Programm zu verstehen!

Im nächsten Abschnitt werden wir uns mit Datenstrukturen beschäftigen.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil III

Datentypen

In C stehen folgende einfache Datentypen zur Verfügung:

char, short, int, long, float, double

Ein char ist ein Datentyp, der genau ein Zeichen (Buchstaben im Sinne des ASCII-Zeichensatzes) aufnehmen kann. Da C nicht explizit zwischen Zeichen und Zahlen unterscheidet, kann dieser Datentyp auch eine Zahl (in Zeichenform) aufnehmen.

Ein int ist ein Datentyp, der eine ganze Zahl aufnehmen kann, wobei der Wertebereich dem konkreten Computer angepasst ist.

Ein short bzw. long ist eine kurze bzw. lange ganze Zahl.

Es gilt, dass ein short kleiner als ein long ist. Ein int entspricht üblicherweise entweder einem short oder einem long. Es ist in ANSI C nicht definiert, wieviel Bytes diese Datentypen zu belegen haben! Der Wertebereich der einzelnen Datentypen steht in der Datei limits.h. Die Verwendung von Include-Dateien wird später erklärt.

Üblicherweise belegt ein char ein Byte, ein short sind 2 Byte und ein long belegt 4 Byte. Ein int belegt, je nach Compiler, 2 oder 4 Byte. Die Datentypen short, int und long sind vorzeichenbehaftet, bei char ist es implementationsabhängig. Die meisten Compiler sind allerdings konsequent und betrachten char als vorzeichenbehaftet.

Zusätzlich kann durch die Schlüsselworte signed und unsigned vor dem Datentyp explizit angegeben werden, dass der Datentyp vorzeichenbehaftet oder vorzeichenlos ist. Damit vergrößert oder verkleinert sich außerdem der darstellbare Bereich. Mit einem Short, der 16 Bit belegt, kann man den Bereich von -32768 bis 32767 abdecken. Ein unsigned short kann den Bereich von 0 bis 65535 abdecken.

Wertebereiche unterschiedlicher Compiler

Typ TC Sozobon GCC
char 8 Bit 8 Bit 8 Bit
  -128 -128 -128
  127 127 127
short 16 Bit 16 Bit 16 Bit
  -32768 -32768 -32768
  32767 32767 32767
int 16 Bit 16 Bit 32 Bit
  -32768 -32768 -2147483648
  32767 32767 2147483647
long 32 Bit 32 Bit 32 Bit
  -2147483648 -2147483648 -2147483648
  2147483647 2147483647 2147483647

Bei den Datentypen float und double handelt es sich um Fließkommazahlen. Damit lassen sich Kommazahlen wie z.B. Preise darstellen. Aufgrund der speziellen Darstellung der Form 0.aaa * 10bb lassen sich auch sehr große Werte darstellen. Zum Beispiel benutzt TC den IEEE-Standard, der für float den Wertebereich von 10E-38 bis 10E+38 ermöglicht. Es steht aber nicht eine Genauigkeit von 38 Stellen zur Verfügung! Um die Genauigkeit zu erhöhen, gibt es den Datentyp double, der zusätzlich noch einen größeren Wertebereich bietet. Der genaue Wertebereich hängt von der Implementierung des Compilers ab. Der Wertebereich von float kann aus der Includedatei float.h ermittelt werden.

Initialisierung

Es ist möglich, einer Variablen gleich bei der Deklaration einen Wert zuzuweisen, d. h. sie zu initialisieren. Dazu schreibt man hinter der Variablen ein Gleichheitszeichen =, gefolgt von dem gewünschten Wert. Der Wert ist eine Zahlenkonstante. Im nächsten Kapitel wird erklärt, wie Konstanten gebildet werden.

const

Um anzuzeigen, dass sich der Wert einer Variablen nicht ändert, kann sie mit dem Attribut const versehen werden. Dies macht allerdings nur bei einer initialisierten Variablen Sinn.

const double e = 2.71828182845905;
const char ich[] = "Michael Bernstein";

Versucht man den Wert einer const-Variablen zu ändern, ist das Ergebnis implementierungsabhängig.

Konstanten

Wird in C eine ganzzahlige Zahl hingeschrieben, handelt es sich um den Datentyp int, sofern keine weitere Angabe gemacht wird. Beginnt die Zahl mit einer Ziffer ungleich 0 (Null), wird von einer Dezimalzahl ausgegangen. Wenn die erste Ziffer eine 0 (Null) ist, handelt es sich um eine Oktalzahl und wenn die Zahl mit 0x eingeleitet wird, ist es eine Hexadezimalzahl.

Es ist zusätzlich noch möglich, den Datentyp zu spezifizieren. Wenn sich direkt an die Ziffern ein l (kleiner Buchstabe L) oder L (wir erinnern uns: Groß- und Kleinschreibung ist in C signifikant!) anschließt, handelt es sich um eine long-Konstante, ein u oder U steht für eine unsigned-Konstante. Ein ul oder UL kennzeichnet eine unsigned-long-Konstante.

Weiterhin gibt es auch Zeichenkonstanten. Dies sind Zeichen in einfachen Hochkommata wie z.B. 'h' und können jeder ganzzahligen Variable zugewiesen werden. Da jedem Zeichen im ASCII-Code auch eine Nummer zugeordnet ist, kann der Compiler eine Zeichenkonstante auch als Zahl betrachten. Für Zeichen, die nicht darstellbar sind, wie z.B. Steuerzeichen für einen Zeilenvorschub, gibt es zwei Möglichkeiten. Zum einen ist es möglich, mit Hilfe des Backslash '\' als Escapezeichen eine Ersatzdarstellung zu benutzen. Da das '\' eine Ersatzdarstellung einleitet, muss dieses Zeichen selbst auch über eine Ersatzdarstellung eingegeben werden. Weiterhin ist es möglich, über den Backslash '\' und einer Folge von ein bis drei Oktalziffern oder dem Backslash '\' einem 'x' und einer Folge von einer oder mehreren hexadezimalen Ziffern jedes gewünschte Zeichen über seine Ordnungszahl einzugeben. Achtung: welches Zeichen letztendlich auf dem Bildschirm erscheint, hängt dann natürlich auch von dem Zeichensatz ab, den der Computer verwendet. Beispiel: Klingel = '\007'; Bell = '\x7';

Es ist auch möglich, Zeichenkonstanten mit mehr als einem Zeichen zu bilden, z.B. für die Zuweisung an ein short, das ja zwei Byte belegt. Da es aber von der Implementierung abhängt, was der Compiler daraus macht, ist meiner Meinung nach der Nutzen nur sehr eingeschränkt.

Ganze Zeichenketten werden in doppelte Hochkommata eingeschlossen. Es gibt in C keinen eigenen Datentyp für Zeichenketten. Im Kapitel Felder wird erklärt, wie man dennoch Zeichenketten in C benutzt.

Boolesche Werte gibt es nicht in C. Ein Wert einer ganzzahligen Variablen von ungleich 0 entspricht einem TRUE, ein Wert von 0 (Null) entspricht einem FALSE. Wir werden bei der Besprechung von Kontrollstrukturen nochmals darauf zurückkommen.

Konstanten vom Typ float oder double kann man, wie man es von reellen Zahlen gewohnt ist, in der Kommadarstellung schreiben, wobei anstelle des Kommas der in den USA gebräuchliche Punkt tritt. Dies sieht dann so aus: 3.14159. Zusätzlich gibt es für sehr kleine oder sehr große Zahlen die wissenschaftliche Darstellung: 34.57E12, was für 34,57 * 1012 steht oder 1.456E-23, was für 1,456 * 10-23 bzw. 1 / (1,456 * 1023 ) steht.

Typkonvertierung

C kann implizit von einem kleineren in einen größeren Datentyp umwandeln. Wenn z.B. einer int-Variablen eine char-Variable zugewiesen wird, kann C automatisch char zu int konvertieren, da der Wertebereich von char in den Wertebereich von int paßt. Auch bei Verknüpfungen in Anweisungen kann diese Konvertierung automatisch erfolgen. Werden z.B. ein int und ein char addiert und einem int zugewiesen, so wird der char automatisch zu einem int konvertiert.

In anderen Fällen, also bei einer Typumwandlung zu einem kleineren Datentyp, muss dem Compiler dies explizit angegeben werden. Dazu dient der cast. Der gewünschte Datentyp wird in Klammern der Variable oder Konstante oder auch einem gesamten Ausdruck vorangestellt.

...
int i = 10;
char j;

j = (char)i;

Die Variable i wird zu einem char gecastet, um sie j zuweisen zu können.

Hierbei muss der Anwender selbst darauf achten, dass der Ergebnisdatentyp auch den kompletten Wert aufnehmen kann. Wird der Wertebereich des Ziels überschritten, kann ein Overflow zu negativen Zahlen auftreten, wenn das Ziel vorzeichenbehaftet ist. Achtung: beim Datentyp char ist nicht definiert, ob er vorzeichenbehaftet oder vorzeichenlos ist!

Schreiben Sie ruhig ein paar kleine Testprogramme, nachdem Sie auch das Kapitel über Anweisungen durchgelesen haben, um sich das Verhalten zu verdeutlichen. Benutzen Sie auch Werte, die den Wertebereich des Ziels überschreiten.

Beispiele für Deklarationen

Wie sieht das Ganze jetzt aus? Dazu folgt ein Miniprogramm, das einfach mal ein paar Variablen deklariert.

char taste;

int main()
{
   int i=0, lauf_index;
   unsigned long anzahl_tests;
}

Gültigkeitsbereich

Eine Variable auf globaler Ebene, also außerhalb einer Funktion, ist in allen Funktionen darunter bekannt, eine Variable innerhalb eines Blockes jedoch nur innerhalb des Blocks, in dem sie deklariert wurde. Innerhalb einer Ebene kann es keine zwei Bezeichner mit dem gleichen Namen geben. Eine Variable innerhalb eines Blocks überdeckt andere Variable gleichen Namens außerhalb.

Damit das Ganze etwas anschaulicher wird und weil schon genug Theorie kam, folgen hierzu einige Beispiele. Testen Sie ruhig alle Beispiele; erst durch eigenes Probieren kommt die nötige Anschauung. Und mit Anschauung kann man sich Dinge leichter merken.

Was passiert bei folgendem Beispiel?

#include <stdio.h>
 
int i=3;
int main()
{
   printf("i = %d\n",i);
}
int i;

Tja, da hier zweimal die Variable i deklariert ist, beschwert sich schon der Compiler und das Programm lässt sich nicht übersetzen.

Was passiert bei folgendem Beispiel?

#include <stdio.h>
 
float main=3.5;
int main()
{
   printf("main = %d\n",main);
}

Auch hier beschwert sich der Compiler. Denn hier haben wir den Namen main zweimal vergeben.

Was passiert bei folgendem Beispiel?

#include <stdio.h>

int i=10;
int main()
{  int i=15;
   printf("i = %d\n",i);
}

Dieses Beispiel sollte sich problemlos übersetzen lassen. Die Ausgabe des Programms ist i = 15. Innerhalb der Funktion main wird die mit 15 initialisierte Variable benutzt. Die global deklarierte und mit 10 initialisierte Variable i ist innerhalb von main unsichtbar, da sie von der inneren Variablen desselben Namens überdeckt wird.

Was passiert bei folgendem Beispiel?

#include <stdio.h>

int i=3;
int main()
{
   printf("j = %d\n",j);
}
int j=5;

Hier sollte sich wieder der Compiler beschweren. Eine globale Variable ist in allen darunter liegenden Funktionen bekannt. Die Funktion main liegt aber über der Deklaration von j und damit kennt main j nicht.

Im nächsten Teil werden wir uns mit Speicherklassen beschäftigen.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil IV

Speicherklassen

Weiterhin kann man auch das Verhalten während der Laufzeit des Programms festlegen. Man spricht in diesem Zusammenhang von Speicherklassen. Da es vom Thema hierher paßt, erkläre ich schon hier kurz, was es damit auf sich hat. Wir werden aber erst im Laufe des Kurses, wenn wir das Wissen einsetzen können, dieses Thema mit Leben füllen.

Um die Speicherklasse einer Variablen anzugeben, kann dem Datentyp das Schlüsselwort auto, static, register, const oder volatile vorangestellt werden:

#include <stdio.h>

int main
{
   static int i=10;
   printf("i = %d\n",i);
}

Alle bisherigen Variablen waren vom Typ auto, d. h. alle außerhalb von Funktionen deklarierte Variablen sind in allen Modulen eines Programms bekannt. Variable innerhalb einer Funktion existieren nur dann, wenn gerade diese Funktion ausgeführt wird. Wird dieser Variable in der Funktion ein Wert zugewiesen, so behält die Variable den Wert nicht bis zum nächsten Funktionsaufruf.

Eine static-Variable außerhalb einer Funktion ist nur innerhalb dieses Moduls (Datei) bekannt. Eine static-Variable innerhalb einer Funktion behält ihren Wert bis zum nächsten Funktionsaufruf bei.

Mit register kann dem Compiler mitgeteilt werden, dass diese Variable häufig benötigt wird und der Zugriff darauf optimiert werden sollte. Dies kann z.B. dadurch geschehen, dass die Variable in ein Register des Prozessors gespeichert wird. Es ist aber dem Compiler überlassen, was er daraus erzeugt.

Mit const zeigt man an, dass man den Wert dieser Variablen nicht mehr ändern möchte. Wir werden auch noch bei Funktionen darauf zurückkommen, wenn wir const-Parameter übergeben.

Mit volatile weist man den Compiler darauf hin, dass der Wert dieser Variable von außerhalb verändert werden kann. Dies kann z.B. ein Register der Hardware in einem Computer sein. Der Sinn ist es, Optimierungen des Compiler zu vermeiden. Er könnte erkennen, dass man einen Wert schreibt und anschließend nur liest und dann den Wert direkt ohne Zugriff auf die Variable verwendet. Wenn nun aber beabsichtigt ist, ein Register der Hardware auszulesen, würde man nur fehlerhafte Werte bekommen.

Reservierte Bezeichner

 auto        default     float       long        sizeof      union
 break       do          for         register    static      unsigned
 case        double      goto        return      struct      void
 char        else        if          short       switch      volatile
 const       enum        int         signed      typedef     while
 continue    extern

Ersatzdarstellungen

 \a  Klingelzeichen        \\    Backslash
 \b  Backspace             \?    Fragezeichen
 \f  Seitenvorschub        \'    Anführungszeichen
 \n  Zeilentrenner         \"    Doppelanführungszeichen
 \r  Wagenrücklauf         \000  oktale Zahl
 \t  Tabulatorzeichen      \xhh  hexadezimale Zahl
 \v  Vertikal-Tabulator

Zahlensysteme

Hier wird es etwas mathematischer, aber durch Beispiele sollte es immer noch recht anschaulich sein. Es ist jedem selbst freigestellt, wie weit er dieses Kapitel verfolgen und verstehen möchte. Ein Verständnis von Zahlensystemen und auch Hex-Zahlen ist allerdings für manche Programme hilfreich.

In jedem Zahlensystem wird eine Zahl als eine Summe von Potenzen einer Basis b dargestellt. Für unser Dezimalsystem mit der Basis 10 bedeutet dies:

345 = 3 * 100 + 4 * 10 + 5 = 3 * 102 + 4 * 101 + 5 * 100

Die Einerstelle gibt also an, wie oft die Basis zur Potenz 0 (wir erinnern uns an dem Mathematikunterricht: eine Zahl hoch 0 ist 1, oder etwas mathematischer x0 := 1) in der Zahl enthalten ist. Das ist bei unserem Beispiel fünfmal. Die Zehnerstelle gibt an, wie oft 101 = 10 enthalten ist usw. Der Wertebereich der einzelnen Stellen ist dabei 0 - (b-1), geht also von 0 bis zu einer Zahl, die um 1 kleiner als die Basis ist. Etwas allgemeiner formuliert lässt sich eine Zahl wie folgt als Summe schreiben:

[Bild]

Da man bei einem Computer sehr leicht die zwei Zustände 5 V (positive Versorgungsspannung) und 0 V (Masse, GND) unterscheiden kann, bietet sich die Basis 2 für die interne Darstellung an. Damit kommen wir zum Binär- oder Dualsystem. Da der Wertebereich immer um 1 kleiner als die Basis ist, haben wir für jede Stelle die zwei Ziffern 0 (Null) und 1 (eins). Jede Einzelne Stelle wird auch Bit genannt. Eine Binärzahl sieht damit wie folgt aus:

 1011012 = 1*25 + 0*24 + 1*23 + 1*22 + 0*21 + 1*20
        = 1*32 + 1*8 + 1*4 + 1*1
        = 32 + 8 + 4 + 1
        = 45

Eigentlich doch ganz einfach, oder? Nur werden solche Zahlen leider etwas lang und unübersichtlich. Wenn man aber vier Stellen des Binärsystems zu einer zusammenfassen könnte, würden ein Byte für zwei Stellen reichen. Mit vier Binärstellen kann man die Zahlen 0 bis 15 (11112 = 23+22+21+20 = 8+4+2+1 = 15) darstellen. also nehmen wir die Basis 16 (wir erinnern uns? Die Basis ist um 1 größer als die größte Ziffer). Das ist dann das Hexadezimalsystem oder kurz Hex-System. Jetzt fehlen nur noch Ziffern für 10 - 15, wir müssen ja für jede Stelle eine einzelnes Zeichen schreiben. Dazu nehmen wir die Buchstaben a - f. Damit sieht unsere Zahl 4510 so aus:

2d16 = 2*161 + 13*160 = 2*16 + 13*1 = 32 + 13 = 45

Analog wird das Oktalsystem zur Basis 8 gebildet. Hier können drei Stellen des Binärsystems zu einer zusammengefasst werden.

Um nun zu erkennen, welches Zahlensystem benutzt wird, kann man der Zahl einen Präfix voranstellen. Eine Hexzahl wird mit einem Dollar '$' oder in C mit einem '0x' markiert. Binärzahlen bekommen oft ein angehängtes 'b'.

Wie kann man nun Zahlen zwischen den Zahlensystemen umrechnen? Für die Umrechnung in das Dezimalsystem reicht obiges Verfahren aus. Um nun aus dem Dezimalsystem in ein anderes System umzurechnen, kann das folgende Verfahren benutzt werden:

Betrachte den Teilerrest, wenn die Zahl durch die neue Basis geteilt wird (modulo). Dies ist die erste Stelle rechts. Teile die Zahl durch die Basis. Verfahre mit dem Ergebnis wie oben für die nächste Stelle, bis das Ergebnis des Teilens 0 ist. Etwas anschaulicher an einem Beispiel:
 45 : 2 = 22 Rest 1  -----------+
 22 : 2 = 11 Rest 0 ----------+ |
 11 : 2 =  5 Rest 1 --------+ | |
  5 : 2 =  2 Rest 1 ------+ | | |
  2 : 2 =  1 Rest 0 ----+ | | | |
  1 : 2 =  0 Rest 1 --+ | | | | |
                      | | | | | |
                      v v v v v v
                      1 0 1 1 0 1

Warum funktioniert das Verfahren? Wenn man sich nochmals die allgemeine Darstellung einer Zahl als Summe vor Augen führt, stellt man fest, dass jede Stelle mit Ausnahme der kleinsten die Basis mit einer Potenz größer gleich 1 als Wertigkeit hat. Und damit ist jede Stelle bis auf die erste glatt durch die Basis teilbar. Lediglich die erste Stelle ist 0 mal durch die Basis teilbar, da die Ziffern nur bis zur Basis minus 1 gehen! Der Teilerrest ist also die Stelle mit der Wertigkeit Basis0. Wenn wir die Zahl durch die Basis teilen, wissen wir, wie oft die Basis1 in der Zahl enthalten ist. Da hier auch wieder größere Potenzen der Basis mit enthalten sein könnten, kann wieder das gleiche Verfahren wie für die erste Stelle benutzt werden. Da eine Beschreibung nur wenig anschaulich ist, sollten Sie selbst versuchen, einige Zahlen zu konvertieren.

Da jede Stelle nur mit positiven Ziffern gebildet wird, kann man so nur positive Zahlen darstellen. Die einfachste Lösung wäre, das oberste Bit als Vorzeichen zu betrachten. Es hat sich aber eine andere Darstellung, nämlich das Zweierkomplement, verbreitet, da hierbei eine Subtraktion auf eine Negierung und anschließende Addition des zweiten Operanden zurückgeführt werden kann. Wie funktioniert das nun? Dazu wird einfach die höchste Stelle immer mit einem negativen Vorzeichen versehen. Wenn wir uns ein char (8 Bit) betrachten, sieht das wie folgt aus:

 001011012 = -0*27 + 0*26 + 1*25 + 0*24 + 1*23 + 1*22 + 0*21 + 1*20
          = 1*32 + 1*8 + 1*4 + 1*1
          = 32 + 8 + 4 + 1
          = 45
 101011012 = -1*27 + 0*26 + 1*25 + 0*24 + 1*23 + 1*22 + 0*21 + 1*20
          = -1*128 + 1*32 + 1*8 + 1*4 + 1*1
          = -128 + 32 + 8 + 4 + 1
          = -83

Wenn man eine Zahl negieren möchte, kann man ganz einfach alle Bits drehen, also aus 1 eine 0 und aus 0 eine 1 machen und anschließend 1 addieren.

 001011012 = -0*27 + 0*26 + 1*25 + 0*24 + 1*23 + 1*22 + 0*21 + 1*20
          = 1*32 + 1*8 + 1*4 + 1*1
          = 32 + 8 + 4 + 1
          = 45
 110100102 = -1*27 + 1*26 + 0*25 + 1*24 + 0*23 + 0*22 + 1*21 + 0*20
          = -1*128 + 1*64 + 1*16 + 1*2
          = -128 + 64 + 16 + 2
          = -46
 110100112 = -1*27 + 1*26 + 0*25 + 1*24 + 0*23 + 0*22 + 1*21 + 1*20
          = -1*128 + 1*64 + 1*16 + 1*2 + 1*1
          = -128 + 64 + 16 + 2 + 1
          = -45

Wer möchte, kann sich dies auch noch in der allgemeinen Summendarstellung anschauen und für eine beliebige Zahl überprüfen. Oder, wie es so schön in Lehrbüchern heißt: der einfache Beweis sei dem Leser überlassen. Dies gilt auch für die Subtraktion als Negierung mit Addition.

Der nächste Abschnitt beschäftigt sich mit Anweisungen.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil V

Anweisungen

Nachdem wir nun Variablen deklarieren können, wollen wir damit auch etwas anfangen, also z.B. damit rechnen. Oder allgemeiner ausgedrückt wollen wir die Variablen in Anweisungen benutzen. Anweisungen werden immer mit einem Semikolon abgeschlossen. Einen Typ von Anweisungen haben wir schon in dem ersten Beispiel gesehen, nämlich einen Funktionsaufruf. Hier schauen wir uns Zuweisungen an Variable und Ausdrücke, als Verknüpfungen von Werten an.

Eine Zuweisung besteht aus dem Namen der Variablen, einem Gleichheitszeichen '=' und dem zugehörigen Wert. Das sieht dann so aus:

#include <stdio.h>
 
 int main()
 {
   int i;
 
   i = 10;
   printf("i hat den Wert: %d\n",i);
 }

Die Variablen werden mit printf ausgegeben und durch Komma getrennt hinter dem Text aufgeführt. Für jede Variable muss im Text an der Stelle, wo sie ausgegeben werden soll, ein Platzhalter angegeben werden. Für den Datentyp int ist dies %d, für float %f, für char %c und für long %ld. Das obige Beispiel gibt den Wert der Variablen i nach dem Doppelpunkt aus. Eine ausführliche Beschreibung finden Sie im Kapitel über die ANSI Libs.

Zusätzlich lassen sich Variable und Konstanten auch in Ausdrücken miteinander verknüpfen. Dazu gehören die bekannten Grundrechenarten wie Addition '+', Subtraktion '-', Multiplikation '*' und Division '/'. Für die Umwandlung zwischen verschiedenen Zahlensysteme wird der Teilerrest benötigt. Dies erledigt der Modulo-Operator '%'. Zum Beispiel liefert 10 % 3 als Ergebnis 1, da der Rest von 10 / 3 eine 1 ergibt.

Es gibt weiterhin noch Inkrement- und Dekrement-Operatoren. Anstelle von i = i + 1 kann man kürzer i++ schreiben. Analog kann man statt i = i - 1 auch i-- schreiben. Der Inkrement- und der Dekrementoperator kann auch vor die Variable geschrieben werden --i. Der Unterschied kommt zum Tragen, wenn die Variable in einem Ausdruck verwendet wird. Steht der Inkrement bzw. Dekrementoperator vor der Variable, so wird sie zuerst inkrementiert bzw. dekrementiert und anschließend ihr Wert benutzt. Andernfalls wird zuerst ihr Wert benutzt und sie dann inkrementiert bzw. dekrementiert. Ein Beispiel findet sich im Kapitel über die while-Schleife.

Um Bits in ganzzahligen Variablen/Konstanten zu verknüpfen, kennt C folgende Operatoren: das Bitweise AND '&', das jedes einzelne Bit der beiden ganzzahligen Operanden mit AND verknüpft. Das bitweise ODER OR '|', das bitweise Exklusiv-Oder (XOR) '^', das bitweise NOT '~' das jedes Bit invertiert und damit das Einer-Komplement liefert und zum Schluss die Schiebebefehle (shift), die sämtliche Bits in ihrer Position um eine vorgegebene Anzahl Stellen verschieben können '<<', '>>'. Beispiele, die das Verhalten verdeutlichen, folgen im Kapitel Bitoperatoren.

Für die Verknüpfung von logischen Werten gibt es auch Operatoren. Das sind die logische Negierung (NOT) '!', das logische und (AND) '&&' und das logische oder (OR) '||'. Hinzu kommen die Vergleiche kleiner '<', kleiner gleich '<=', größer '>', größer gleich '>=', gleich '==' und ungleich '!='. Achtung: Der Test auf Gleichheit besteht aus zwei Gleichzeichen! Ein einzelnes Gleichzeichen wäre eine Zuweisung. Beispiel für die Anwendung sind im Kapitel logische Operatoren zu finden.

Es ist auch noch eine Kurzform einer Anweisung möglich. Wenn die Variable, der das Ergebnis zugewiesen wird, auch in den Ausdruck eingeht. In diesem Fall kann man den entsprechenden Operator vor die Zuweisung schreiben und die Variable im Ausdruck weglassen. Der Ausdruck i = i * 3; lässt sich auch als i *= 3; schreiben.

Der Komma-Operator erlaubt es, zwei Ausdrücke als eine Operation zu schreiben. Dies ist in der Regel nicht nötig und wird hauptsächlich in der for-Schleife benutzt. Deshalb findet sich auch dort ein Beispiel.

Zum Schluss haben wir noch den Fragezeichenoperator. Hierbei handelt es sich um die Kurzform einer bedingten Anweisung. Wenn der Ausdruck links vom Operator wahr ist, also ungleich 0, dann wird die Anweisung zwischen Fragezeichen und Doppelpunkt ausgeführt. Andernfalls wird die Anweisung rechts vom Doppelpunkt ausgeführt. Bei den Anweisungen muss es nicht ein kompletter Ausdruck sein, eine Konstante oder Variable für eine Zuweisung ist auch möglich.

Und damit das etwas anschaulicher wird, folgt ein Beispiel. Wir nehmen an, wir haben 2 Variablen Zahl1 und Zahl2 und wollen das Minimum der Variablen Min zuweisen. Dann lässt sich das wie folgt schreiben:

...
Min = (Zahl1 < Zahl2) ? Zahl1 : Zahl2;

In Anweisungen können Klammern gesetzt werden, um dadurch zum einen eine bestimmte Reihenfolge der Auswertung vorzugeben. Zum anderen können sie das Verständnis erleichtern.

Wir können auch eine Zuweisung in den Ausdruck einbauen, das Ergebnis dieser Zuweisung ist der Wert dieses Operanden: x = (i = 10) + 2; In diesem Fall hat x den Wert von i + 2, also 12.

Bitoperatoren

Um zu verstehen, wie die Bitoperatoren funktionieren, können die folgenden Tabellen benutzt werden. Sie beschreiben für ein Bit das Ergebnis für alle möglichen Kombinationen der beiden Operanden für AND, OR und XOR.

AND-Verknüpfung
  0 1
0 0 0
1 0 1
OR-Verknüpfung
  0 1
0 0 1
1 1 1
XOR-Verknüpfung
  0 1
0 0 1
1 1 0

Und dazu folgen jetzt ein paar Beispiele. Was liefern diese Beispiele? Um zu prüfen, ob Sie alles verstanden haben, sollten Sie das Ergebnis zuerst selbst ausrechnen und anschließend mit einem kleinen C-Programm prüfen, bevor Sie weiter lesen.

...
i1 = 10;
i2 = i1 & 0x00FF;
i3 = i1 | 172;
i4 = ~i3;
i5 = i3 ^ i2;

In der nächsten Folge werden Ihnen die Lösungen für die obigen Aufgaben präsentiert.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil VI

Auflösung der Beispiele für Bitoperatoren

Um das Ergebnis zu berechnen, schreiben wir die Konstanten als Binärzahl. i1 hat den Wert 10, binär 1010. Hier sollte noch kein Problem sein, da es nur eine einfache Zuweisung ist. Die Konstante 0x00FF lautet binär 0000000011111111. Damit ergibt die Verknüpfung:

1010 & 0000000011111111 = 1010 = 10

Das bitweise und kann also benutzt werden, um bestimmte Bits einer Variable so stehen zu lassen, wie sie gesetzt sind und um andere zu löschen, d. h. zu maskieren.

Anschließend verodern wir den Inhalt von i1, der nach der Zuweisung immer noch 10 ist, mit der Konstante 172

i3 = i1 | 172 = 10 | 172 = 1010 | 10101100 = 10101110 = 174

Im Ergebnis werden die Bits gesetzt, die in i3 oder i1 gesetzt sind. Wenn die Schnittmenge leer ist, entspricht dies einer Addition.

Jetzt werden alle Bits negiert und an i4 zugewiesen.

i4 = ~i3 = ~ 182 = ~ 11001010 = 00110101 = 110101 = 32+16+4+1 = 53

Haben Sie auch dieses Ergebnis errechnet? Haben Sie sich gewundert, dass das Programm zur Überprüfung ein anderes Ergebnis lieferte? Wenn i4 vom Datentyp int ist, belegt er 16 bzw. 32 Bit (je nach Compiler). Wir müssen natürlich für das Invertieren sämtliche Bits betrachten. Dies bedeutet für Turbo C i4 = ~i3 = 1111111100110101. Wenn i4 eine vorzeichenbehaftete Zahl ist, dann ist die Zahl negativ, wenn das höchste Bit gesetzt wird. Wie im Kapitel über Zahlensysteme ausführlich erklärt, wird im Zweierkomplemment die höchste Potenz negativ gezählt. Das ergibt für i4 = -215 + 214 + ... +22 + 20 = -175.

Und zum Schluss noch exklusiv oder von i3 mit dem Wert 174 und i2 mit 10.

i5 = i3 ^ i2 = 10101110 ^ 1010 = 10100100 = 164

Die Schiebebefehle verknüpfen 2 Operanden

i6 = i1 << 2;
i7 = i6 >> 2;

Dies können wir ganz einfach prüfen, indem wir uns wieder die Bits hinschreiben. Für die Zuweisung an i6 werden sämtliche Bits aus i1 um 2 Stellen nach links verschoben. Überzählige Stellen fallen links heraus, rechts werden Nullen nachgeschoben. Damit ist i6 = 101000 = 40 und wenn wir alles wieder zurückschieben und links Nullen nachfüllen, kommen wir wieder auf das ursprüngliche Ergebnis, da keine gesetzten Bits weggefallen sind. Wer sich nochmals das Binärsystem anschaut, wird erkennen, dass eine Verschiebung um ein Stelle nach links einer Multiplikation mit 2 entspricht.

Logische Operatoren

Die logischen Operatoren können beliebig mit Vergleichen und auch mit arithmetischen und Bitoperatoren verknüpft werden.

...
i1 = 1;
i2 = 10;
i3 = 27;
i4 = (i2 <= i3) && i1;

Der Wert von i1 ist ungleich Null und entspricht damit einem Wahr. Der Vergleich von i2 und i3 liefert einen Wert ungleich Null, da i2 kleiner als i3 ist. Das verknüpft mit einem logischen und mit i1 liefert einen Wert ungleich 0 als Ergebnis, was einem wahr entspricht.

Prioritäten

Vergleichen Sie einmal das Ergebnis der beiden folgenden Ausdrücke:

...
i1 = 12 * 3 + 5;
i2 = 12 + 5 * 3;

Das erste Beispiel sollte 41 ergeben, das zweite 27. Also hat wohl die Punktrechnung eine höhere Rangfolge als die Strichrechnung, genau wie man es in der Schule gelernt hat. Die Rangfolge der Operatoren ist im Kapitel Priorität der Operatoren aufgeführt. Wenn zwei Operatoren die gleiche Priorität haben, ist nicht definiert, in welcher Reihenfolge sie ausgeführt werden.

Dies kann zu Problemen führen, wenn sich ein Programm darauf verlässt. Nehmen wir als Beispiel eine Addition von 2 Funktionsaufrufen: x = f() + g(). Wenn sowohl f() als auch g() eine Variable ändern, von der das Ergebnis von f und g abhängt, kann eine andere Reihenfolge zu einem anderen Ergebnis von x führen.

Bei den logischen Operatoren AND und OR wird üblicherweise eine Auswertung von links her solange vorgenommen, bis das Ergebnis feststeht. Ein Beispiel, das zwar nicht unbedingt guter Programmierstil ist, aber die Gefahr dieser Auswertung verdeutlicht:

...
k = 10;
l = 12;
j = 3;
i = ( l < k) && (j = 10);

Welchen Wert hat j? Es ist 3, denn l ist größer als k. Damit steht der Wert des logischen AND schon fest. Ein falsch verknüpft mit irgendwas liefert immer falsch. Der zweite Ausdruck wird deshalb nicht mehr ausgewertet und damit bekommt j nicht den Wert 10 zugewiesen.

Als Fazit bleibt, dass man niemals so programmieren sollte, dass man sich darauf verlässt, dass bestimmte Teile eines Ausdrucks in einer bestimmten Reihenfolge ausgeführt werden! Solche Seiteneffekte können zu schwer zu findenden Fehlern führen.

Priorität der Operatoren

Die Operatoren haben folgende Priorität:

 Operator                       Auswertung von
 
 ()  []  .  ->                  links -> rechts
 ! ~ - ++ -- & * (type) sizeof  rechts -> links
 *  /  %                        links -> rechts
 +  -                           links -> rechts
 <<  >>                         links -> rechts
 <  <=  <  >=                   links -> rechts
 ==  !=                         links -> rechts
 &                              links -> rechts
 ^                              links -> rechts
 |                              links -> rechts
 &&                             links -> rechts
 ||                             links -> rechts
 ?:                             links -> rechts
 =  +=  -=  etc.                rechts -> links
 ,                              links -> rechts

Cookie-Jar

Der Cookie-Jar ist eine Tabelle. Jeder Eintrag besteht aus einem 32-Bit-Wert als Kennung des Cookies und einem 32-Bit-Wert als Wert des Cookies. Die Adresse des Cookies steht in der Speicheradresse $5A0. Enthält diese Adresse den Wert 0, gibt es auf diesem System keinen Cookie-Jar. Ab TOS 1.06 initialisiert das BIOS selbst den Cookie-Jar und trägt dort System-Cookies ein, die die Hardware beschreiben. Das Ende des Cookie-Jars wird durch einen Cookie mit dem Wert 0 angezeigt. Dieser Cookie enthält als Wert die Anzahl Cookies, die in den Cookie-Jar passen.

Für die Kennung des Cookies gilt, dass er druckbar sein und ein Kürzel ergeben sollte, das die Bedeutung des Cookies beschreibt. mit "_" beginnende Kennungen sind für ATARI reserviert.

Mehr über den Cookie-Jar kann im Profibuch nachgelesen werden. Leider ist dieses sehr gute und umfangreiche Nachschlagewerk nicht mehr neu erhältlich.

Weitere Möglichkeiten für Variablendeklarationen

Felder

Um viele gleichartige Variablen zusammenzufassen, gibt es die Felder bzw. Arrays. Felder werden deklariert, indem hinter dem Variablennamen in eckigen Klammern die Anzahl der gewünschten Elemente steht. Wenn in Ausdrücken auf ein einzelnes Element zugegriffen werden soll, schreibt man hinter dem Variablennamen in eckigen Klammern die Nummer bzw. den Feldindex. Der Feldindex läuft in C immer von 0 bis (Anzahl Elemente -1). Das erste Element hat also den Index 0, das letzte mit Index 1 eines weniger als das Feld Elemente hat. Wie sieht das nun aus?

...
int i1, i2, i3, iFeld[3];

iFeld[0] = 1;
iFeld[1] = 2;
iFeld[2] = 3;
i1 = iFeld[0];
i2 = iFeld[1] * 3;
i3 = 5 + iFeld[i1];

Dieses Beispiel deklariert ein Feld mit drei Elementen, die zuerst gesetzt und anschließend in Anweisungen benutzt werden. Als Feldindex wird in der letzten Zeile keine Konstante, sondern eine Variable benutzt. Ein Feldindex muss lediglich ganzzahlig sein. Es kann auch wiederum ein Ausdruck sein. Der Feldindex kann auch berechnet werden. Der Compiler übernimmt keine Überprüfung, ob der Feldindex im gültigen Bereich liegt! Dies ist bei Ausdrücken als Feldindex auch nicht mehr möglich! In dem Beispiel kann man also auf iFeld[3] zugreifen, ohne dass es eine Fehlermeldung des Compilers gibt. Zur Laufzeit erfolgt auch keine Überprüfung, da sie viel Zeit kostet und bei dynamisch allozierten Feldern nur schwer zu realisieren ist. Ein Schreibzugriff kann dabei Daten überschreiben, die anderweitig benutzt werden und zu Fehlfunktionen, im ungünstigsten Fall auch zum Absturz führen!

Felder lassen sich auch initialisieren. Dazu werden die Werte durch geschweifte Klammern zusammengefasst und durch Kommata getrennt:

...
int iFeld[3] = { 1, 2, 3 }

Es ist auch möglich, mehrdimensionale Felder zu deklarieren. Dazu wird für jede Dimension die Größe in eigene eckige Klammern geschrieben.

...
int iFeld[3][2] = { 1, 2, 3, 4, 5, 6 }, i;
int jFeld[3][2] = { {1, 2}, {3, 4}, {5, 6} }, i;

i = iFeld[2][1];

Anschaulich ist dies ein Feld mit drei Elementen, bei dem jedes Element wiederum ein Feld mit jeweils zwei Elementen vom Typ int ist. Deshalb werden in der Initialisierung die Werte für den gleichen ersten Index direkt hintereinander geschrieben. Das zweite Beispiel jFeld setzt zur Verdeutlichung noch zusätzliche geschweifte Klammern.

In C gibt es keinen speziellen Datentyp für Zeichenketten bzw. Strings. Eine Zeichenkette ist ein Feld mit Elementen vom Typ char. Das Ende der Zeichenkette wird durch ein Zeichen mit der Nummer 0 markiert. Dieses String-Ende kann auch als Zeichenkonstante '\0' dargestellt werden.

Es ist möglich, bei einer Initialisierung die Größe des Feldes wegzulassen. Zeichenketten können auch mit einer Zeichenkettenkonstante anstelle der Angabe der einzelnen Felder in geschweiften Klammern initialisiert werden.

#include <stdio.h>
#include <string.h>
 
int main()
{
   char str[] = "Hallo";
 
   printf("Länge der Zeichenkette: %ld Zeichen\n",strlen(str));
   printf("Größe des Feldes str  : %ld Zeichen\n",sizeof(str));
}

Die hier benutzten Funktionen werden später noch ausführlich erklärt. Die Funktion strlen liefert die Anzahl Zeichen des Strings. Das abschließende '\0' wird hierbei nicht mitgezählt. Deshalb ist das Ergebnis dieser Ausgabe 5. Da das String-Ende natürlich auch in dem Feld Platz finden muss, ist die zweite Ausgabe, die die Größe eines Objekts im Speicher liefert, 6. Dies wird wichtig, wenn der Speicher für eine Zeichenkette dynamisch alloziert wird! Beispiele dazu finden sich später.

Strukturen

Sollen unterschiedliche Datentypen zu einem zusammengefasst werden, bietet sich dafür eine Struktur bzw. struct an. Eine Variable, die ein struct ist, wird deklariert, indem das Schlüsselwort struct und anschließend in geschweiften Klammern die einzelnen Komponenten mit Datentyp und einem Namen angegeben werden.

Die Struktur kann auch einen Namen erhalten, der nach dem Schlüsselwort struct angegeben wird. Über diesen Namen können weitere Variablen deklariert werden, ohne dass der Aufbau der Struktur nochmals angegeben werden muss.

Auf eine Einzelne Komponente eines struct wird zugegriffen, indem dem Namen der Variable ein Punkt folgt, gefolgt vom Namen der gewünschten Komponente. Die einzelnen Bestandteile eines struct müssen nicht notwendigerweise verschiedene Datentypen sein. Wenn es für das Verständnis günstiger ist, über einen Namen und nicht über ein Feldindex zuzugreifen, wie z.B. bei komplexen Zahlen, bietet sich ein struct an.

int main()
{
   struct complex {
      float real;
      float imaginaer;
   };
   struct {
      int i;
      long j;
   } a;
   struct complex c;

   a.i = 10;
}

In dem Beispiel wird eine Variable a deklariert, die vom Datentyp struct ist. Als Beispiel für den Zugriff auf die Komponenten wird die Komponente i auf den Wert 10 gesetzt. Über die Variable a wird ein struct mit dem Namen complex deklariert. Hier wird aber mit der Strukturdeklaration keine Variable deklariert. Weiter unten wird eine Variable c deklariert, die vom Typ struct ist. Hier wird nur der Name der Struktur angegeben; der Aufbau der Struktur wurde schon oben beschrieben und muss nicht nochmals wiederholt werden. Ein struct kann auch mehr als 2 Komponenten wie in dem Beispiel haben.

Damit können wir zum angekündigten Beispiel kommen, dem Cookie-Jar. Eine kurze Erklärung, was der Cookie-Jars ist und wie er aufgebaut ist, findet sich im Kapitel Cookie-Jar. Da ein Cookie aus zwei Teilen besteht, die jeweils vier Byte belegen, bietet sich ein struct an. Als Komponenten sind sowohl Felder mit 4 char als auch long bzw. unsigned long möglich. Wegen der einfacheren Handhabung, z.B. Vergleiche, wählen wir unsigned long. Damit haben wir das erste Teilstück des Programms cookie-1.c, das wir in den weiteren Kapiteln ausbauen.

...
struct cookie {
   unsigned long name;
   unsigned long value;
};

Da im Cookie-Jar beide 4 Byte-Werte direkt hintereinander im Speicher stehen, wäre es nützlich, wenn die Struktur im Speicher genauso angeordnet ist. Diese Ausrichtung oder Alignment ist vom Compiler und Maschine abhängig und kann bei einigen Compilern im Quelltext mit Hilfe des Präprozessors eingestellt werden. Auf 68K-Prozessoren wird üblicherweise ein Alignment von 2 Bytes benutzt, um Variablen auf gerade Adressen zu legen. Bei TC ist das Alignment 1 Byte, wenn char kombiniert werden, andernfalls 2 Bytes, und kann nicht geändert werden. Das Alignment ist meines Wissens für die verschiedenen Compiler nicht dokumentiert.

enum

Mit einem enum bzw. Aufzählungsdatentyp lassen sich Konstanten für unterschiedliche Werte einer Variablen definieren. Eingeleitet wird eine Aufzählung mit dem Schlüsselwort enum, gefolgt von geschweiften Klammern und den möglichen Werten, getrennt durch Kommata, innerhalb der Klammern. Damit ähnelt das Beispiel demjenigen für Strukturen.

int main()
{
   enum ampel { ROT, GELB, GRUEN };
   enum { EINS, ZWEI, DREI } a;
   enum ampel c;

   a = EINS;
}

Hier wird eine Variable vom Typ enum deklariert, die damit den Wertbereich EINS, ZWEI und DREI hat. Die Variable a wird auf den Wert EINS gesetzt. Es ist genauso möglich, einen enum mit einem Namen zu deklarieren und diesen Namen später zu benutzen, ohne den Wertebereich nochmals aufzuführen.

Der ersten Konstante in einem enum ordnet der Compiler dem Wert 0 zu, die weiteren werden aufsteigend durchnumeriert. Damit haben in dem Beispiel die Konstanten folgende Werte:

0 ROT EINS
1 GELB ZWEI
2 GRUEN DREI

Es ist möglich, Konstanten in einem enum einen bestimmten Wert zuzuordnen. Die danach aufgeführten Konstanten werden aufsteigend ab dem angegebenen Wert nummeriert:

int main()
{
   enum { EINS=1, ZWEI, DREI } a;

   a = EINS;
}

Damit haben die Konstanten folgende Werte:

1 EINS
2 ZWEI
3 DREI

Es ist auch möglich, mehreren Konstanten den gleichen Wert zuzuweisen.

union

Wenn z.B. in Abhängigkeit einer anderen Information alternativ unterschiedliche Datentypen gespeichert werden sollen, bietet sich eine union an. Nach der Beschreibung von struct und enum können wir die Benutzung schon erraten. Auch hier lassen sich mehr als zwei Komponenten zusammenfassen.

int main()
{
   union test1 {
      char x;
      int y;
   };
   union {
      int Wert;
      char Feld[2];
   } x;
   union test1 y;

   x.Wert = 1;
}

Auf die unterschiedlichen Komponenten einer union wird genau wie bei einem struct über den Namen der Komponente zugegriffen. Allerdings belegen sämtliche Komponenten ein und denselben Speicherplatz. Der für die union angelegte Speicherplatz ist der Platz der größten Komponente, unabhängig davon, welche Komponente benutzt wird. Damit lässt sich nur eine Komponente zur gleichen Zeit sinnvoll nutzen. Alternativ lässt sich auch eine union benutzen, um auf den gleichen Speicher auf verschiedene Weise zuzugreifen. Da der Name eines Cookies in der Regel gemäß einer Empfehlung von Atari ein Kürzel darstellt, wäre es wünschenswert, auch auf die einzelnen Zeichen des Namens zuzugreifen. Damit lässt sich der struct für den cookie wie folgt erweitern:

...
struct cookie {
   union {
      unsigned long name_long;
      char name_array[4];
   } name;
   unsigned long value;
};

typedef

Es wäre schön, wenn Datentypen, die man mit den oben aufgeführten Methoden bildet, auch so benutzen kann, wie die direkt in C vorhandenen, d. h. wenn man also einfach den Namen eines Datentyps gefolgt von dem Variablennamen schreiben kann. Dazu gibt es die Möglichkeit, mit typedef einen Datentyp zu definieren. Vom Syntax sieht eine Typdefinition aus wie eine Deklaration einer Variablen, der wie eine Speicherklasse das Schlüsselwort typedef vorangestellt ist.

int main()
{
   typedef struct {
      int real;
      int imag;
   } complex;
   complex c;

   c.real = 10;
}

Dieses Beispiel definiert einen Datentyp mit dem Namen complex und darunter eine Variable vom Typ complex. Die Definition des Datentyps kann wie jede andere Deklaration auch außerhalb der Funktion stattfinden. Mit diesem Wissen können wir mit unserer Cookie-Struktur einen Datentyp definieren.

#include 'cookie-1.wml'

Bitfelder

Wenn eine Reihe von Informationen, die nur zwei Zustände besitzen (z.B. ein, aus), abgelegt werden soll, kann jede dieser Informationen in einem Bit gespeichert werden. Sämtliche Zustände können dann in einem long oder int zusammengefasst werden. Zum Beispiel kann ein Bild farbig sein, gepackt sein, ...

#define IS_FARBE  0x01
#define IS_PACKED 0x02

int flags = IS_FARBE | IS_PACKED;
int uncomp = farbe & ~IS_PACKED;

Die einzelnen Bits müssen nun allerdings mittels der Bitoperationen gesetzt und gelöscht werden. Einfacher ist es, ein Bitfeld zu definieren. Ein Bitfeld wird wie eine Struktur benutzt. Jede Komponente ist eine Menge von Bits und wird Bitfeld genannt. Jede Komponente muss deshalb vom Typ int sein, besser aber unsigned int, und erhält einen Doppelpunkt gefolgt von der Anzahl Bits. Für Zwischenräume können leere Bitfelder angelegt werden, die nur aus einem Doppelpunkt und der Anzahl Bits ohne Namen bestehen. Mit der Anzahl 0 kann ein Bitfeld auf eine Wortgrenze ausgerichtet werden. Benutzt werden die Bitfelder wie Strukturen.

struct {
   unsigned int is_farbe : 1;
   unsigned int is_packed : 1;
} flags;

flags.is_farbe = 1;

Leider ist die Reihenfolge der Bits und auch, ob ein Bitfeld eine Wortgrenze überschreiten kann, von der Implementierung abhängig. Damit sind Bitfelder zwar sehr praktisch, um externe Daten wie z.B. die Register von einem Controller zu beschreiben, leider sind solche Programme aber nicht mehr portabel.

Zeiger

Ein Zeiger oder Pointer ist die Adresse einer Variablen. Bisher hat der Compiler bei Variablen selbst entschieden, wo eine Variable im Speicher liegt. Mit dem Namen der Variablen hat man den Wert bekommen. Mit einem Pointer speichert man die Adresse, wo der Wert zu finden ist. Wie wird nun ein Zeiger deklariert? Dazu wird bei einer Deklaration vor den Namen der Variablen ein * gesetzt und anstelle einer Variablen eines bestimmten Datentyps hat man einen Zeiger auf diesen Typ deklariert.

...
int i,*ptri;

Dieses kleine Beispiel deklariert eine Variable vom Typ int mit dem Namen i und einen Zeiger auf einen int mit dem Namen ptri. Um auf den Wert, auf den der Zeiger zeigt, zuzugreifen, muss der Zeiger dereferenziert werden. Dazu schreibt man wiederum einen * vor die Variable, wenn man sie in einem Ausdruck benutzt.

...
i = *ptri;

Nur, wo liegt denn jetzt eigentlich der Speicher, auf den unser Pointer zeigt? Hierfür ist der Programmierer selbst verantwortlich! Hieraus ergibt sich die Mächtigkeit, aber auch die Fehlerträchtigkeit von Pointern. Deshalb werden wir uns auch genug Zeit lassen und mit Hilfe von Beispielen die Arbeit mit Pointern und das Verhalten anschaulich darstellen.

Eine Möglichkeit ist es, sich die Adresse einer Variablen mit dem Adreßoperator & geben zu lassen. Dazu folgt ein kleines Beispielprogramm:

#include <stdio.h>
 
int main(void)
{
   int i,j,p;

   i = 3
   p = &i;
   j = *p;
   printf("i = %d, j = %d\n",i,j);
}

Dieses Beispiel setzt i auf den Wert 3. Dann wird p auf den Wert der Adresse von i gesetzt. Anschließend wird der Wert, auf den p zeigt, j zugewiesen. Danach hat j den Wert 3, denn der Wert, auf den p zeigt, ist natürlich i.

Damit das noch anschaulicher wird, malen wir uns einfach mal einen möglichen Speicher auf und tragen die entsprechenden Werte ein:

 +-----+-----+-----+-----+-----+
 I     I     I     I     I     I
 +-----+-----+-----+-----+-----+
    0     1     2     3     4

Der Compiler legt jetzt in diesen einfachen Speicher unsere drei Variablen an. Wir machen es uns jetzt einfach und ordnen jede Variable einer Zelle zu:

 +-----+-----+-----+-----+-----+
 |     |     |     |     |     |
 +-----+-----+-----+-----+-----+
    0     1     2     3     4
    i     j     p

Jetzt schreiben wir den Wert 3 in die Variable i:

 +-----+-----+-----+-----+-----+
 |  3  |     |     |     |     |
 +-----+-----+-----+-----+-----+
    0     1     2     3     4
    i     j     p

Nun wird in die Variable p die Adresse von i geschrieben. Wir sehen, dass i in der Zelle 0 abgespeichert ist. Also hat p den Wert 0:

 +-----+-----+-----+-----+-----+
 |  3  |     |  0  |     |     |
 +-----+-----+-----+-----+-----+
    0     1     2     3     4
    i     j     p

Nun schreiben wir den Wert, auf den p zeigt, in die Variable j. Wir sehen, dass p auf die Adresse 0 zeigt, also die Adresse von i. Und was dort steht, wird j zugewiesen. Auf der Adresse 0 steht 3, wir haben ja die 3 der Variablen i zugewiesen. Damit bekommen wir:

 +-----+-----+-----+-----+-----+
 |  3  |  3  |  0  |     |     |
 +-----+-----+-----+-----+-----+
    0     1     2     3     4
    i     j     p

In vielen Beispielen wird durch Pfeile gezeigt, auf welche Variable ein Pointer zeigt. Das könnte dann folgendermaßen in unser Beispiel integriert werden:

 +-----+-----+-----+-----+-----+
 |  3  |  3  |  0  |     |     |
 +-----+-----+-----+-----+-----+
    0     1     2     3     4
    i <+  j     p
       |        |
       +--------+

Eine weitere Möglichkeit, den Speicher für einen Zeiger zu verwalten, ist die Verwendung der Funktionen malloc, free, ... um sich damit einen Speicherbereich zuweisen zu lassen. Beispiele hierzu folgen später.

Zeiger und Felder

Zeiger und Felder sind in C verwandt und beide Darstellungsarten können zum Teil alternativ benutzt werden. Der Name eines Feldes ohne die eckigen Klammern entspricht der Anfangsadresse eines Feldes und kann einem Zeiger auf den Datentyp der Feldelemente zugewiesen werden. An den Namen des Feldes kann natürlich keine Zuweisung erfolgen. Durch die die Deklaration des Feldes ist der dazugehörige Speicher angelegt worden und kann deshalb nicht mehr verlegt werden.

...
int *x;   /* Zeiger auf einen int */
int f[3]; /* Feld mit 3 int */
x = f;    /* x zeigt jetzt auf das erste Element von f */
f = x;    /* verboten !! */

Um den Pointer auf das nächste Feldelement zu setzen, ist es nicht nötig, diese Adresse dem Pointer zuzuweisen. Auch mit Pointern ist Arithmetik möglich. Wird ein Wert zu einem Pointer addiert oder von einem Pointer subtrahiert, so wird der Pointer tatsächlich um den Wert multipliziert mit der Größe des Datentyps verändert. Wird in obigen Beispiel x++; als nächste Anweisung geschrieben, so zeigt x auf den nächsten int, also auf f[1] und nicht auf das nächste Byte im Speicher.

...
int *x;   /* Zeiger auf einen int */
int f[3]; /* Feld mit 3 int */

x = f;    /* x zeigt auf f[0] */
x++;      /* x zeigt auf f[1] */
x++;      /* x zeigt auf f[2] */
x++;      /* x zeigt hinter f!! */

Da keine Überprüfung stattfinden kann, ist es auch möglich, den Zeiger auf Speicher zeigen zu lassen, der von uns nicht in irgendeiner Form reserviert wurde. Damit werden möglicherweise Variablen überschrieben, die noch anderweitig benötigt werden. Ein Absturz des Programms kann die Folge sein.

Es lässt sich auch mit der Pointerschreibweise auf ein Feld zugreifen.

...
int i,f[[3];

i = *f;
i = *(f+1);  /* entspricht i = f[1]; */

Und die Feldschreibweise kann auf Pointer angewandt werden.

...
int i,*p;

i = f[0];
i = p[1];

Zeiger und Strukturen

Für die Verwendung von Zeigern auf Strukturen existiert noch eine Ersatzdarstellung für den Zugriff auf Komponenten. Mit dem bisherigen Wissen würde man den Zeiger mit dem * dereferenzieren und anschließend mit dem Punkt auf die Komponente zugreifen.

...
struct {
   x:int;
   y:int;
   z:int;
} *pImag;
int i;

i = (*pImag).x;

Da der Punkt für den Zugriff auf die Komponenten eine höhere Priorität hat, muss die Dereferenzierung geklammert werden. Diesen Zugriff kann man mit einem Pfeil -> abkürzen. Damit sieht das Beispiel wie folgt aus:

...
struct {
   x:int;
   y:int;
   z:int;
} *pImag;
int i;

i = pImag->x;

Achtung: Da das Beispiel nur das Prinzip zeigt, ist hier nicht dafür gesorgt worden, dass der Zeiger auf einen definierten Speicherbereich zeigt.

Zeiger auf Zeiger

Es ist möglich, Zeiger auf jeden Datentyp zu deklarieren. Also auch auf einen Zeiger, was vielleicht auf den ersten Blick etwas verwirrend ist. Notwendig ist dies aber z.B. schon beim Zugriff auf den Cookie-Jar. Hier existiert eine Systemvariable, die den Zeiger auf den Cookie-Jar enthält. Von dieser Systemvariable ist die Adresse bekannt. Wenn wir dafür eine Variable anlegen wollen, benötigen wir einen Zeiger, der auf einen Zeiger zeigt, der auf den Cookie-Jar zeigt. Genau das macht das folgende Beispiel. Was die ersten Zeilen mit dem Lattenkreuz bzw. Hash nun genau bedeuten, wird im Kapitel über den Preprozessor erklärt. Sie dienen dazu, das Programm an Unterschiede zwischen den verschiedenen Compilern anzupassen. Der Zugriff auf das Betriebssystem gehört nicht zu den genormten Teilen eines ANSI-Compilers.

#ifdef __TURBOC__
#include <tos.h>
#else
#ifdef __GNUC__
#include <osbind.h>
#else
#include <tosbind.h>
#endif
#endif


typedef struct cookie_entry {
   union {
      unsigned long name_long;
      char name_array[4];
   } name;
   unsigned long value;
} CookieEntry;
 
int main(void)
{  CookieEntry **CookieJarPtr, *CookieJar;
   long OldStack;

   OldStack=Super(0L);
   CookieJarPtr = (CookieEntry**)0x5a0L;
   CookieJar=*CookieJarPtr;
   Super((void *)OldStack);
   return 0;
}

Um auf die Systemvariable zuzugreifen, wurde ein Zeiger CookieJarPtr angelegt, der auf die Adresse 0x5a0 gesetzt wird, wo der Zeiger auf den Cookie-Jar steht. Anschließend wird diese Adresse ausgelesen, also auf den Wert zugegriffen, auf den der Pointer zeigt, um den Zeiger auf den Cookie-Jar zu erhalten. Da auf diese Systemvariable nur im Supervisormodus zugegriffen werden kann, sind die beiden Anweisungen in die Aufrufe der Betriebssystemfunktion Super geklammert. Eine Beschreibung dieser Funktionen ist allerdings nicht Bestandteil dieses Kurses.

Die Zeiger kann man natürlich auch grafisch darstellen:

 +------------+     +----------+     +-----++-----+
 |CookieJarPtr|---->|_p_cookies|---->|     ||     |
 +------------+     +----------+     |name ||name |
                       0x5a0         |     ||     |
                     +---------+     |value||value|
                     |CookieJar|---->|     ||     |
                     +---------+     +-----++-----+
 
                                Cookie

Der Zeiger CookieJarPtrd auf eine Adresse gesetzt, die einen Zeiger auf den Cookie-Jar enthält. Dies ist die Systemvariable _p_cookies. Der Zeiger CookieJar wird auf den gleichen Wert gesetzt und beide zeigen nun auf den ersten Cookie. In dem Cookie-Jar stehen die Cookies direkt hintereinander. Der Cookie-Jar ist also ein Feld bzw. array. Allerdings ist seine Größe nicht vorgegeben.

Die nächste Folge beschäftigt sich mit Kontrollstrukturen.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil VII

Kontrollstrukturen

Bis jetzt ist es nur möglich, Anweisungen der Reihe nach abzuarbeiten. Für etwas komplexere Programme ist es aber erforderlich, den Ablauf des Programms noch weiter zu kontrollieren. Dazu gibt es Möglichkeiten, Anweisungen in Abhängigkeit von Bedingungen auszuführen und auch Anweisungen kontrolliert zu wiederholen.

Spielen Sie ruhig etwas mit den folgenden Beispielen herum. Setzen Sie sie in main ein, deklarieren benutzte Variable, falls die Deklarationen noch nicht vorhanden sind und setzen Sie sie auf sinnvolle Werte. Das sind zum einen die Werte, die abgefragt werden, das sind aber auch andere Werte für den Fehlerfall. Erst durch solche Übungen kommt die nötige Anschauung.

if

Die if-Anweisung dient dazu, in Abhängigkeit von einer Bedingung Anweisungen auszuführen. Zusätzlich können mit einem else-Befehl Anweisungen für den Nicht-if-Fall ausgeführt werden. Die Bedingung wird in Klammern hinter dem if aufgeführt und ist ein Ausdruck, der 0 oder ungleich 0 ist. Ist der Ausdruck ungleich 0, wird die dahinter stehende Anweisung bzw. der dahinter stehende Block ausgeführt. Wir erinnern uns, in C gibt es keinen logischen Datentyp.

Und damit das etwas anschaulicher wird, hier einige Beispiele:

...
if (i!=0) printf("i ist nicht 0");

if (i)
   printf("i ist nicht 0");

if (i==0)
   printf("i ist 0");

if (!i)
   printf("i ist 0");

if (i==0)
   printf("i ist 0");
else
   printf("i ist nicht 0");

if (i!=0)
{
   printf("i ist nicht 0");
   i=0;
}
 
if (i=3)
   printf("i ist nicht 0");

Was machen jetzt die einzelnen Beispiele?

Das erste Beispiel prüft, ob i ungleich 0 ist und gibt dann einen Text aus.

Das zweite Beispiel macht genau das Gleiche wie das erste. Allerdings wird hier ausgenutzt, dass ein Wert ungleich 0 schon einem true entspricht. Wenn die Variable aber nicht schon für logische Werte benutzt wird, sollte der Vergleich wie im ersten Beispiel geschrieben werden. Dies erleichtert es, das Programm zu verstehen. Die Anweisung wurde in die nächste Zeile geschrieben und eingerückt. Auch wenn häufig in anderen Programmen die Anweisung in die gleiche Zeile geschrieben wird, wie im Beispiel 1, sollte dem Einrücken der Vorzug gegeben werden. Damit ist die Anweisung leichter erkennbar. Dies gilt erst recht, wenn es sich um eine leere Anweisung mit lediglich einem Semikolon handelt.

Das dritte Beispiel prüft auf Gleichheit mit 0.

Und das vierte Beispiel zeigt eine kürzere Form. Ein Wert von 0 entspricht einem false und wird negiert zu einem true.

Das fünfte Beispiel zeigt die Anwendung von else. Wenn i den Wert 0 hat, die Bedingung also zutrifft, wird die erste Ausgabe gemacht, im anderen Fall die zweite Ausgabe nach dem Schlüsselwort else. Ist im else-Fall wieder eine if-Abfrage nötig, werden üblicherweise die Blöcke nicht weiter eingerückt, sondern das if direkt hinter das else geschrieben. Dies erhöht die Übersichtlichkeit solcher Abfragen beträchtlich.

...
/* if mit Einrückung */
if (i==3)
   printf("i ist 3");
else
{
   if (i==4)
      printf("i ist 4");
   else
      if (i==5)
         printf("i ist 5");
}
 
/* Die übersichtliche Einrückung */
if (i==3)
   printf("i ist 3");
else if (i==4)
   printf("i ist 4");
else if (i==5)
   printf("i ist 5");

Das sechste Beispiel hat keine Anweisung für den if-Fall, sondern einen Block.

Das siebte Beispiel enthält eine Zuweisung als Ausdruck. Das Ergebnis der Zuweisung ist auch das Ergebnis des Ausdrucks und kann deshalb auch als Bedingung benutzt werden. Solche Abkürzungen sollten aber vermieden werden, da die Zuweisung so etwas versteckt und damit das Programm schwerer zu verstehen ist. Deshalb geben viele Compiler an dieser Stelle eine Warnung aus. Achtung! Wird versehentlich bei einem Vergleich auf Gleichheit ein Gleichzeichen vergessen, kann der Ausdruck stattdessen eine Zuweisung ergeben und damit einen schwer zu findenden Fehler.

Da das else bei geschachtelten if-Anweisungen zu dem letzten if gehört, muss hier unter Umständen mit geschweiften Klammern gearbeitet werden! Auch dazu ein Beispiel, hier soll das else zu dem ersten if gehören:

...
/* Falsch */
if (i==3)
   if (j==5)
      x=3;
else
   x=1;

/* Das entspricht */
if (i==3)
{
   if (j==5)
      x=3;
   else
      x= 1;
}

/* Gewollt */
if (i==3)
{
   if (j==5)
      x=3;
}
else
   x=1;

Die if-Anweisung bauen wir auch sofort in unser Cookie-Programm ein, denn wir müssen ja prüfen, ob die Systemvariable überhaupt einen Zeiger auf den Cookie-Jar enthält.

#ifdef __TURBOC__
#include <tos.h>
#else
#ifdef __GNUC__
#include <osbind.h>
#else
#include <tosbind.h>
#endif
#endif
#include <stdio.h>

typedef struct cookie_entry {
   union {
      unsigned long name_long;
      char name_array[4];
   } name;
   unsigned long value;
} CookieEntry;

int main(void)
{  CookieEntry **CookieJarPtr, *CookieJar;
   long OldStack;

   OldStack=Super(0L);
   CookieJarPtr = (CookieEntry**)0x5a0L;
   CookieJar=*CookieJarPtr;
   Super((void *)OldStack);
   if (CookieJar == (CookieEntry *)0)
      printf("Dieses System hat keinen Cookie Jar\n");
   else
   {
      /* Hier können wir jetzt die Cookies ausgeben */
   }
   return 0;
}

switch

Soll eine Variable oder das Ergebnis eines Ausdrucks mit Konstanten verglichen werden, kann hierzu die switch-Anweisung benutzt werden. Nach dem Schlüsselwort switch folgt in Klammern der Ausdruck, darunter innerhalb geschweifter Klammern die Konstanten. Jeder Konstante geht ein case voraus und nach der Konstanten folgt ein Doppelpunkt und die Anweisungen für diese Konstante. Das sieht dann so aus:

...
switch (i)
{
   case 3:
      printf("i ist 3");
      break;
   case 4:
      printf("i ist 4");
   case 5:
      printf("i ist 5");
      break;
   default:
      printf("nix passt");
}

Sobald das erste case mit einer passenden Konstanten gefunden wird, werden die dahinter stehenden Anweisungen ausgeführt. Ein weiteres case führt nicht zu einem Abbruch der switch-Anweisung. Dies muss explizit mit einem break gemacht werden. Da es in der Regel nicht erwünscht ist, wenn die Anweisungen des darunter liegenden case ausgeführt werden, sollte es kommentiert werden, wenn es erwünscht ist.

...
switch (i)
{
   case 3:
      printf("i ist 3");
      break;
   case 4:
      printf("i ist 4");
      /* fall through */
   case 5:
      printf("i ist 5");
      break;
   default:
      printf("nix passt");
}

Es kann zusätzlich noch mittels default ein Fall angegeben werden, der immer dann ausgeführt wird, wenn sämtliche andere Konstanten nicht zutreffen.

while

Die while-Schleife erlaubt es, eine Bedingung zu prüfen und Anweisungen so oft zu wiederholen, wie die Bedingung einen Wert ungleich 0 ergibt. Diese Bedingung wird hinter dem Schlüsselwort while in Klammern angegeben. Da die Bedingung vor dem Schleifendurchlauf überprüft wird, kann es auch vorkommen, dass die Schleife überhaupt nicht durchlaufen wird.

Bei den Anweisungen kann es sich um eine Einzelne Anweisung oder einen Block (dann in geschweiften Klammern) handeln.

int i;
 
i = 0;
while (i++ < 5)
{
   printf("i = %d\n",i);
}
i = 0;
while (++i < 5)
{
   printf("i = %d\n",i);
}

Dieses Beispiel zeigt auch die Auswirkung für eine vorangestellten und einen nachgestellten Inkrementoperator.

Mit dieser Schleife können wir jetzt den Cookie-Jar ausgeben, nachdem wir den Zeiger auf den Cookie-Jar ermittelt und bereits geprüft haben, ob ein Cookie-Jar existiert.

#ifdef __TURBOC__
#include <tos.h>
#else
#ifdef __GNUC__
#include <osbind.h>
#else
#include <tosbind.h>
#endif
#endif
#include <stdio.h>

typedefstruct cookie_entry {
   union{
      unsignedlong name_long;
      char name_array[4];
   } name;
   unsignedlong value;
} CookieEntry;

int main(void)
{  CookieEntry **CookieJarPtr, *CookieJar;
   long OldStack;

   OldStack=Super(0L);
   CookieJarPtr = (CookieEntry**)0x5a0L;
   CookieJar=*CookieJarPtr;
   Super((void *)OldStack);
   if (CookieJar == (CookieEntry *)0)
      printf("Dieses System hat keinen Cookie Jar\n");
   else
   {
      while (CookieJar->name.name_long != 0)
      {
         printf("Name des Cookies: %d\n", CookieJar->name.name_long);
         printf("Wert des Cookies: %d\n", CookieJar->value);
         CookieJar++;
      }
   }
   return 0;
}

Achtung! Damit die Schleife terminiert, muss mindestens eine Anweisung vorhanden sein, die einen Einfluss auf die Bedingung in dem Schleifenkopf hat, also z.B. eine Variable, die abgefragt wird, auf einen anderen Wert setzt.

do-while

Die do-while Schleife prüft nach dem Schleifendurchlauf eine Bedingung. Deshalb wird diese Schleife immer mindestens einmal durchlaufen. Diese Schleife besteht aus dem Schlüsselwort do, einer Anweisung oder einem Block von Anweisungen in geschweiften Klammern, dem Schlüsselwort while und in Klammern eine Bedingung. Die Schleife wird so lange durchlaufen, wie die Bedingung einen Wert ungleich 0 ergibt.

...
int i, f[10];

i = 0;
do {
   f[i] = i;
   i++;
} while (i < 10);

Damit entspricht die do-while Schleife der repeat-until-Schleife in Pascal und Modula. Aber Achtung: hier liegt eine mögliche Fehlerquelle für Pascal- und Modula-Programmierer. Während bei der repeat-until-Schleife die Bedingung für den Abbruch der Schleife angegeben wird, wird bei der do-while-Schleife die Bedingung für den Schleifendurchlauf angegeben!

for

Die for-Schleife erlaubt es, Anweisungen mehrfach zu wiederholen und dazu in dem Kontrollblock der Schleife eine Initialisierung, eine Bedingung für den Schleifendurchlauf und eine Aktion für jeden Schleifendurchlauf anzugeben. Nach dem Schlüsselwort for folgt in Klammern, durch Semikolon getrennt, zunächst eine Anweisung, die vor dem ersten Schleifendurchlauf ausgeführt wird. Anschließend folgt ein Ausdruck, der vor jedem Schleifendurchlauf geprüft wird und einen Wert ungleich 0 ergeben muss, damit die Schleife ausgeführt wird. Zum Schluss folgt wieder eine Anweisung, die nach jedem Durchlauf der Schleife ausgeführt wird. Nach diesem Schleifenkopf folgt eine Anweisung oder ein Block in geschweiften Klammern als Schleifenrumpf. Es ist auch möglich, Teile wie z.B. die Initialisierung wegzulassen, also eine leere Anweisung zu schreiben. Dies macht für die Abbruchbedingung allerdings wenig Sinn.

Ein Beispiel sind Schleifen, bei denen die Anzahl der Durchläufe bekannt ist. Das folgende Beispiel setzt sämtliche Elemente eines Feldes auf den Index.

...
int i,f[10];

 for (i=0;i<10;i++)
{
   f[i] = i;
}

Der Schleifenzähler i wird vor dem ersten Durchlauf mit 0 initialisiert und nach jedem Schleifendurchlauf inkrementiert. Die Schleife wird so lange ausgeführt, wie i kleiner 10 ist. Sie können sich das Verhalten durch zusätzliche Ausgaben verdeutlichen.

Um auch mehr als einen Ausdruck zu verwenden, kann der Komma-Operator eingesetzt werden:

...
int i, j, f[10], g[10];

for (i=0, j=9; i<10; i++, j--)
{
   f[i] = g[j];
}

Die for-Schleife ist nicht nur auf Schleifen mit einem Schleifenzähler anwendbar. Das folgende Beispiel initialisiert einen Pointer und prüft den Wert des Pointers:

...
char *p, *Text="Hallo";

for (p=Text; *p != '\0'; p++)
   printf("%c\n",*p);

Auch wenn es möglich ist, sehr komplexe Ausdrücke in den Schleifenkopf zu stecken, sollte man dennoch nur die Anweisungen dort hinschreiben, die für den Abbruch der Schleife relevant sind. In obigem Beispiel sind dies also das Initialisieren und Inkrementieren des Pointers. Dadurch bleiben die Programme übersichtlich.

continue

Die continue-Anweisung dient dazu, in Schleifen den aktuellen Durchlauf abzubrechen. Es wird sofort die Bedingung überprüft und gegebenfalls mit dem nächsten Schleifendurchlauf begonnen. Bei for-Schleifen wird vorher die Anweisung für jeden Durchlauf ausgeführt. Und das sieht dann wie folgt aus:

...
int i, a[10];

for (i=0; i<10; i++)
{
   if (a[i] < 0)
      continue;
   a[i] = a[i] * 10;
}

In diesem Beispiel wird mit dem nächsten Schleifendurchlauf fortgefahren, wenn das Feldelement negativ ist. Wer das Beispiel testen möchte, sollte das Feld zuerst mit sinnvollen Werten vorbesetzen.

Da durch die continue Anweisung etwas verschleiert wird, dass die darunter stehenden Anweisungen nicht in jedem Fall ausgeführt werden, sollte nach Möglichkeit darauf verzichtet werden. Das obige Beispiel lässt sich auch umformulieren.

...
int i, a[10];

for (i=0; i<10; i++)
{
   if (a[i] >= 0)
      a[i] = a[i] * 10;
}

Wenn dadurch die folgenden Teile zu weit eingerückt werden, so ist zu überlegen, ob nicht sinnvollerweise Teile besser in separaten Funktionen aufgehoben sind.

break

Die break-Anweisung dient dazu, Schleifen und die switch-Anweisung zu verlassen und mit der ersten Anweisung hinter der Schleife bzw. switch fortzufahren.

...
char *p, *Text="Hallo";

for (p=Text; ; p++)
{
   if (*p == '\0')
      break;
   printf("%c\n",*p);
}

Da mit der break-Anweisung ein Nebenausgang aus einer Schleife geschaffen wird, ist damit der Programmablauf schwerer zu erkennen. Es sollte deshalb nach Möglichkeit in Schleifen auf ein break verzichtet und die Schleife über eine geeignete Bedingung im Schleifenkopf verlassen werden.

goto

Auch C kennt ein goto, mit dem zu einer beliebigen Marke innerhalb der gleichen Funktion gesprungen werden kann. Dazu wird nach dem Schlüsselwort goto der Name der Marke angegeben. Die Marke wird durch den Namen, gefolgt von einem Doppelpunkt, gesetzt und kann vor jeder Beliebigen Anweisung stehen. Eine mögliche sinnvolle Anwendung könnte das Verlassen von verschachtelten Schleifen für eine Fehlerbehandlung sein, da ein break nur die aktuelle Schleife (und nicht mehrere, ineinander geschachtelte Schleifen auf einmal) verlassen kann. Das unvollständige Beispiel unten zeigt die Anwendung.

...
for ( ... )
{
   for ( ... )
   {
      if ( Fehler_passiert )
         goto error;
   }
}
error:
   Fehlerbehandlung;

Allerdings muss jetzt die Fehlerbehandlung für den normalen Programmlauf umgangen werden. Wie man sieht, wird es schwieriger, den Programmlauf bei Anwendung von goto zu verfolgen. Es ist auch immer möglich, ein Programm ohne goto zu formulieren. Deshalb sollte ein goto vermieden werden. In meinen über 10 Jahren Erfahrung als Softwareentwickler habe ich niemals ein goto benutzen müssen.

Funktionen

Eine Funktion dient dazu, eine Menge von Anweisungen zusammenzufassen und unter einem Namen anzusprechen. Ihr können Werte mitgegeben werden, um die Aufgabe für verschiedene Ausgangssituationen durchzuführen. Diese Werte nennt man Parameter. Die Funktion kann ein Ergebnis zurückliefern, das in Ausdrücken verwendet werden kann.

Eine Funktion besteht aus dem Funktionskopf und dem Rumpf in geschweiften Klammern, der Deklarationen und Anweisungen enthalten kann.

Der Funktionskopf wiederum besteht aus dem Datentyp des Wertes, den die Funktion zurückliefert. Daran schließt sich der Funktionsname an. Anschließend folgen in geschweiften Klammern die Parameter. Die Parameter sind eine Liste von Datentypen und Namen des Parameters, durch Komma getrennt.

Eine Funktion kann keine zusammengesetzten Datentypen wie Strukturen oder Felder zurückliefern.

int Addiere(int x,int y)
{
   return x+y;
}

int main(void)
{  int x;

   x = Addiere(3,4);
   return 0;
}

Diese Funktion liefert einen int zurück, heißt Addiere und hat zwei Parameter x und y vom Typ int.

Beendet wird die Funktion entweder bei Erreichen der letzten geschweiften Klammer oder mit dem Operator return. Soll die Funktion einen Wert zurück geben, wird dieser Wert als Parameter des Operators return angegeben. Die Funktion Addiere gibt den Wert x+y zurück, also die Summe. Liefert die Funktion keinen Wert zurück, ist also typlos, kann das Schlüsselwort void verwendet werden. Ein Beispiel findet sich in dem Kapitel über offene Felder. Genauso kann bei einer Funktion ohne Parameter in die Klammern void geschrieben werden.

Aufgerufen wird die Funktion durch ihren Namen gefolgt von Klammern. Wenn die Funktion Parameter erwartet, werden diese in den Klammern durch Komma getrennt angegeben. Der Returnwert einer Funktion muss nicht in einem Ausdruck verwendet werden, sie kann auch dann, wenn sie nicht vom Typ void ist, ignoriert werden. Eine Funktion mit Returnwerten kann wie eine Prozedur in Pascal verwendet werden. Dies ist allerdings in der Regel nicht sinnvoll. Denn entweder ist der Returnwert das Ergebnis der Berechnung der Funktion und wird deshalb benötigt - oder es handelt sich um einen Fehlerwert, der deshalb auch abgefragt werden sollte.

Eine Funktion innerhalb einer Funktion (wie z.B. in Pascal) zu schreiben, ist nicht möglich.

Um ein Programm leichter verständlich zu machen, sollten alle Variablen, die von außerhalb der Funktion benutzt werden sollen, als Parameter übergeben werden. Ein Zugriff auf globale Variablen versteckt, welche Funktionen welche Variablen verändern. Ausnahmen sind dann sinnvoll, wenn kleine Funktionen sehr häufig aufgerufen werden und der Aufruf auf Geschwindigkeit optimiert werden muss.

main

Die Funktion mit dem Namen main muss in einem Programm genau einmal vorhanden sein. Sie ist die erste Funktion, die durch den Programmstart aufgerufen wird. Daher ist auch vorgegeben, welche Parameter und welcher Returnwert möglich sind.

Der Returnwert ist immer int. Diesen Wert kann das Programm, das unser Programm gestartet hat, abfragen. Dies wird z.B. in Batchprogrammen oder Shellscripts ausgenutzt. Normalerweise steht eine Returnwert von 0 für eine fehlerfreie Ausführung.

Parameter können entweder nicht akzeptiert werden (wie es z.B. bei Programmen üblich ist, die eine grafische Benutzeroberfläche haben) oder main bekommt die Parameter übergeben, die ein Aufrufer in die Kommandozeile geschrieben hat. In diesem Fall ist der erste Parameter vom Typ int und gibt an, wieviele Parameter dem Programm übergeben wurden. Der erste Parameter für das Programm enthält üblicherweise den Programmnamen, also das erste Wort der eingegebenen Kommandozeile. Es ist auf dem Atari vom Startupcode abhängig, ob der Programmname korrekt übergeben wird. Der zweite Parameter von main ist ein Feld von Zeigern auf die einzelnen Parameter des Programms.

#include <stdio.h>
 
int main(int argc, char *argv[])
{  int i;

   printf("%d Parameter bekommen\n",argc);
   for (i=0;i<argc;i++)
      printf("Parameter %d = %s\n",i,argv[i]);
   return 0;
}

Dieses Programm sollte von einem Kommandointerpreter gestartet werden, um unterschiedliche Parameter zu übergeben.

Prototypen

Für den Geltungsbereich gilt, wie bei Deklarationen, dass eine Funktion nur unterhalb ihrer Definition bekannt ist. Eine Funktion, die von main aufgerufen wird, muss also über main stehen. Wenn dies nicht erwünscht ist, um z.B. die Funktion main in einem Programm schneller zu finden, kann ein Prototyp angegeben und die Funktion an einer weiter unten liegenden Stelle definiert werden. Ein Prototyp ist der Funktionskopf mit einem Semikolon anstelle des Funktionsrumpfes.

int Addiere(int x, int y);

int main(void)
{  int x;

   x = Addiere(3,4);
   return 0;
}

int Addiere(int x, int y)
{
   return x+y;
}

In dem Prototyp kann darauf verzichtet werden, die Namen der Parameter anzugeben. Der Compiler prüft lediglich die Typen der Parameter anhand des Prototypen. Allerdings fördert die Angabe der Namen das Verständnis des Programms. Es ist nicht mehr erforderlich, in der Funktion selbst nachzuschauen.

int Addiere(int, int);

int main(void)
{  int x;

   x = Addiere(3,4);
   return 0;
}

int Addiere(int x, int y)
{
   return x+y;
}

Offene Felder

Wenn Felder als Parameter übergeben werden, ist es nicht nötig, die Größe des Feldes explizit anzugeben. Da der Name eines Feldes ohne eckige Klammern der Adresse des ersten Elementes entspricht, können auch Felder übergeben werden, wo ein Pointer erwartet wird und umgekehrt.

void Upcase(char Zeile[])
{  int i;

   for (i=0; Zeile[i] != '\0'; i++)
      if (Zeile[i]>='a' && Zeile[i]<='z')
         Zeile[i] = Zeile[i] - 'a' + 'A';
}

int main(void)
{  char *Text = "Hallo";

   Upcase(Text);
   return 0;
}

Der nächste Teil beschäftigt sich mit den Möglichkeiten, Parameter zu verändern.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil VIII

Parameter verändern

In C sind sämtliche Parameter Kopien. Eine Funktion kann deshalb nicht einen Parameter so ändern, dass der Aufrufer von dieser Änderung Notiz nimmt.

#include <stdio.h>
 
void Rechne(int a)
{
   a = 10 * a;
}

int main(void)
{  int i = 3;

   printf("i vor dem Aufruf: %d\n",i);
   Rechne(i);
   printf("i nach dem Aufruf: %d\n",i);
   return 0;
}

Sollen die Parameter verändert werden, z.B. weil es Strukturen sind, die als Returnwerte nicht zulässig sind, muss ein Zeiger auf diesen Datentyp übergeben werden:

#include <stdio.h>

void Rechne(int *a)
{
   *a = 10 * (*a);
}

int main(void)
{  int i = 3;

   printf("i vor dem Aufruf: %d\n",i);
   Rechne(&i);
   printf("i nach dem Aufruf: %d\n",i);
   return 0;
}

Man beachte, dass jetzt in dem Aufruf natürlich auch der Adressoperator benutzt wird, um die Adresse der Variablen zu ermitteln. Da der Name eines Feldes der Adresse des ersten Elements entspricht, können Elemente von Feldern auch verändert werden. Ein Beispiel dazu findet sich im Kapitel über offene Felder.

const-Parameter

Um anzuzeigen, dass übergebene Felder oder die Variable, auf die ein Pointer zeigt, nicht verändert werden, kann man das Attribut const benutzen:

int string_laenge(const char p[])
{  int i=0;

   while (p[i] != 0)
      i++;
   return i+1;
}

int main(void)
{  int i;

   i = string_laenge("Hallo");
}

Im obigen Beispiel wird der Funktion eine Zeichenkettenkonstante als Parameter übergeben. Die Übergabe von Konstanten an Funktionen, die den Parameter ändern, liefert unvorhersagbare Ergebnisse. Das Attribut const erlaubt es festzustellen, ob eine Funktion auch mit Konstanten als Parameter keine Probleme bereitet.

Alte Deklarationsformen

Es ist möglich, den Typ der Funktion und die Parameter nicht anzugeben. Der Compiler geht dann davon aus, dass die Funktion einen int zurückliefert. Der Compiler ist aber nicht mehr in der Lage, zu prüfen, ob die Funktion mit den richtigen Parametern aufgerufen wird. Werden in dem Prototypen Parameter und Typ der Funktion weggelassen, wird damit auch das Verständnis des Programms erschwert. Denn nun muss man die Definition der Funktion suchen, um Informationen über Parameter zu bekommen.

a()
{
   return 3;
}

int main(void)
{  int i;

   i = a();
   return 0;
}

Es ist zwar auch möglich, die Parameter in den Klammern nur mit Namen, getrennt durch Kommata, anzugeben und darunter die Parameter nochmals mit ihrem Datentyp aufzuführen. Diese Vorgehensweise ist aber ein Überbleibsel von K&R C und sollte nicht mehr benutzt werden.

int a(x, y)
int x;
int y;
{
   return x+y;
}

Rekursionen

Eine Funktion kann sich selbst wieder aufrufen. Man spricht dann von einer Rekursion. Jede Rekursion benötigt eine Abbruchbedingung, in der sie sich nicht selbst aufruft. Andernfalls erhält man eine Endlosrekursion.

Ein klassisches Beispiel für eine Rekursion ist die Berechnung der Fakultät einer Zahl, da die Definition der Fakultät rekursiv ist. Allerdings lässt sich die Fakultät nichtrekursiv schneller berechnen. Die Fakultät von 1 (geschrieben 1!) ist definiert als 1. Die Fakultät einer beliebigen Zahl n ist diese Zahl multipliziert mit der Fakultät der um eins kleineren Zahl. Oder mathematisch formuliert: n! = n * (n-1)!

int fakultaet(int x)
{
   if (x == 1)
      return 1;
   else
      return x * fakultaet(x - 1);
}

Achtung! Die Fakultät erreicht sehr schnell große Werte, so dass man mit einem 16-Bit-int-Wert schnell an die Grenzen stoßen kann. Außerdem prüft obige Funktion nicht, ob der Parameter auch positiv ist. Machen Sie sich ruhig den Programmlauf klar, indem Sie Ausgaben einfügen und die Funktion in main aufrufen.

Oder versuchen Sie sich an den Türmen von Hanoi. Hierbei haben Sie 3 Stäbe, auf die Sie Scheiben aufschieben können. Jede Scheibe kann nur auf einen leeren Stab oder auf einen Stab mit einer größeren Scheibe geschoben werden. Wie verschiebt man jetzt den Turm von Scheiben auf einen anderen Stab? Auch dieses Problem kann man rekursiv lösen. Man nimmt einfach den Turm aus allen Scheiben bis auf die letzte, schiebt ihn auf den Stab, wo später der Turm nicht hin soll. Dann schiebt man die letzte Scheibe auf den Zielstab und anschließend den geparkten Turm auf diese Scheibe. Der kleinere Turm wird natürlich auch wieder mit dem gleichen Algorithmus verschoben. Erst wenn man einen Turm aus nur einer Scheibe hat, ist die Abbruchbedingung erreicht. Diese Scheibe kann direkt verschoben werden.

Funktionspointer

Auch für Funktionen können Zeiger auf Funktionen definiert werden. Und über diese Zeiger können die Funktionen auch wieder aufgerufen werden. Da hier genau auf die Auswertungsreihenfolge der Operatoren geachtet werden muss, ist den Funktionspointern ein eigenes kleines Kapitel gewidmet. Wir erinnern uns an die Zeiger:

int *a;

a ist ein Zeiger auf ein int.

int * d(void);

Da die Klammern eine höhere Priorität haben, ist dies eine Funktion d mit dem Returnwert int *. Damit wir einen Funktionspointer haben, muss also der * an den Namen der Funktion gebunden werden. Also setzen wir Klammern:

int (*d)(void);

Dies ist jetzt ein Zeiger d vom Typ Funktion ohne Parameter mit einem Returnwert int. Wenn wir den Zeiger verwenden wollen, muss er wieder dereferenziert werden. Da auch hier der * eine geringere Priorität als die Klammern für den Parameter haben, muss die Dereferenzierung geklammert werden.

Die Adresse einer Funktion bekommt man, analog der Adresse von Feldern, einfach durch den Namen ohne Klammern:

#include <stdio.h>

int a(int x,int y)
{
   return x+y;
}

int main(void)
{  int (*p)(int,int);

   p = a;
   printf("Summe = %d\n",(*p)(3,4));
   return 0;
}

Funktionspointer lassen sich immer dann verwenden, wenn ein Algorithmus unabhängig von einem konkreten Datentyp formuliert werden soll. Ein Sortieralgorithmus beispielsweise muss die Daten vergleichen; er benötigt also eine Vergleichsfunktion. Ein Beispiel findet sich in der ANSI Lib. Wer einmal in die Datei stdlib.h nachschaut, findet dort die QuickSort-Funktion (qsort):

void    qsort( void *base, size_t nmemb, size_t size,
          int (*compar)() );

Oder ein Zeichenprogramm verwaltet grafische Objekte und zu jedem Objekt gehört eine passende Zeichenfunktion, die das Objekt malen kann. Wenn als Zeiger auf die eigentlichen Objektdaten ein typloser Zeiger benutzt wird, muss die Verwaltung keine Kenntnisse der Objekte haben. Wir nähern uns damit schon den Möglichkeiten der objektorientierten Programmierung, wenn wir unterschiedliche Daten mit der gleichen Schnittstelle behandeln können.

Cookie-Jar

Und zum Schluss nochmals zu unserem Cookie-Jar. Wir können jetzt einige Teile auch in Funktionen auslagern.

#ifdef __TURBOC__
#include <tos.h>
#else
#ifdef __GNUC__
#include <osbind.h>
#else
#include <tosbind.h>
#endif
#endif
#include <stdio.h>

typedef struct cookie_entry {
   union {
      unsigned long name_long;
      char name_array[4];
   } name;
   unsigned long value;
} CookieEntry;

CookieEntry *GetCookieJar(void)
{  long OldStack;
   CookieEntry *CookieJar;

   OldStack = Super(0L);
   CookieJar = *((CookieEntry**)0x5a0L);
   Super((void *)OldStack);
   return CookieJar;
}

void PrintCookie(CookieEntry *Cookie)
{
   printf("Name des Cookies: %d\n", Cookie->name.name_long);
   printf("Wert des Cookies: %d\n", Cookie->value);
}

int IsNullCookie(CookieEntry *Cookie)
{
   return (Cookie->name.name_long == 0);
}

void TraverseCookieJar(CookieEntry *Cookie)
{
   while (!IsNullCookie(Cookie))
   {
      PrintCookie(Cookie);
      Cookie++;
   }
}

int main(void)
{  CookieEntry *CookieJar;

   CookieJar = GetCookieJar();
   if (CookieJar == (CookieEntry *)0)
      printf("Dieses System hat keinen Cookie Jar\n");
   else
      TraverseCookieJar(CookieJar);
   return 0;
}

Was haben wir jetzt gewonnen, ausser mehr Code? Zum einen können die Funktionen an anderer Stelle wiederverwendet werden - auch in einem anderen Programmen, wie wir im Kapitel über modulares Kompilieren sehen werden. Zum anderen wird das Programm leichter verständlich. Denn jede Teilaufgabe ist in eine Funktion verschoben worden, die ihre Aufgabe auch als Namen trägt. Und diese Vorgehensweise erlaubt es, ein Programm nicht sofort komplett verstehen oder auch programmieren zu müssen. Zuerst nimmt man sich main vor. Wir ermitteln den Cookie-Jar, und wenn wir einen Zeiger darauf bekommen, durchwandern wir ihn. Eigentlich doch ganz einfach. Und dann können wir uns einer der beiden Funktionen zuwenden und sie weiter analysieren. Bei der Entwicklung wird diese Vorgehensweise top down (von oben nach unten) genannt. Wir zerlegen das große Problem in kleinere Teilprobleme, die wir nacheinander lösen. Diese Teilprobleme kann man wieder weiter zerlegen. Dies führt man solange durch, bis sich das Teilproblem leicht lösen lässt.

Der folgende Teil beschäftigt sich mit dem Stack.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil IX

Stack

Jedes Programm benutzt einen Speicher als Stack. Ein Stack ist ein Stapel (last in, first out), d. h. das, was zuletzt auf den Stack gelegt wird, wird auch zuerst wieder herausgenommen. Er wird vom Programm dazu benutzt, die Parameter für einen Funktionsaufruf und auch lokale Variablen aufzunehmen. Der Stack wächst von großen Adressen zu kleinen Adressen.

Ein C-Programm legt bei einem Funktionsaufruf üblicherweise die Parameter beginnend von rechts der Reihe nach auf den Stack. Hat sich die Funktion beendet, entfernt der Aufrufer die Parameter vom Stack. TC und PC benutzen in der Grundeinstellung allerdings Register für die Parameterübergabe. Die Übergabe über den Stack kann auch mit dem Schlüsselwort cdecl erzwungen werden.

Ein Pascal-Programm legt die Parameter von links nach rechts auf den Stack und die aufgerufenen Funktion entfernt die Parameter vom Stack. Diese Art der Parameterübergabe kann mit dem Schlüsselwort pdecl erzwungen werden.

cdecl bzw. pdecl werden zwischen Returnwert und Funktionsnamen angegeben:

int cdecl Test(int i)
{
   return i;
}

Mit diesem Wissen kann nun auch die erforderliche Stackgröße abgeschätzt werden. Für jede Funktion, die in der Aufrufhierarchie eine Ebene tiefer geht, muss der Platz, den die Parameter und die lokalen auto-Variablen dieser Funktion belegen, addiert werden. Bei einer Rekursion wäre dies also jeder Aufruf von sich selbst. Für Funktionen, die nacheinander aus der gleichen Funktion aufgerufen werden, darf der Speicherverbrauch natürlich nicht einfach aufsummiert werden, sondern beide Aufrufe werden getrennt betrachtet; für eine Abschätzung ist das Maximum relevant.

Preprozessor

Was bis jetzt noch nicht erklärt wurde, sind die Zeilen mit dem Lattenkreuz bzw. Hash '#' als ersten Buchstaben. Es handelt sich hierbei um Anweisungen für den Preprozessor. Der Preprozessor ist in der Regel ein eigenständiges Programm, das vor dem eigentlichen Compiler den Quelltext bearbeitet. Eine Anweisung für den Preprozessor kann an jeder Beliebigen Stelle im Quelltext stehen, muss aber eine eigene Zeile belegen. Natürlich muss das Ergebnis nach dem Preprozessorauf wieder ein korrektes C-Programm ergeben. Im Folgenden werden die Möglichkeiten des Preprozesors der Reihe nach besprochen.

#include <stdio.h>

Die Include-Anweisung fügt den Inhalt der angegebenen Datei in den Quellcode ein. Ist der Name wie im obigen Beispiel in spitze Klammern gesetzt, wird nur das Verzeichnis nach dieser Datei durchsucht, das bei der Installation des Compilers als Include-Verzeichnis angegeben wurde. Dies wird für die mit dem Compiler mitgelieferten Include-Dateien benötigt. Wird der Name in doppelten Hochkommata angegeben, wird zuerst das aktuelle Verzeichnis und danach das Systemverzeichnis durchsucht. Dies wird ausgenutzt, um ein Programm modular zu compilieren und dazu entsprechende Include-Dateien anlegen zu können. Dazu finden sich Beispiele im Kapitel über modulares Kompilieren.

#define PI 3.141592

Mit der define-Anweisung wird eine Regel für Textersetzungen definiert. Überall, wo im Quellcode der erste Text auftaucht, wird er durch den zweiten ersetzt. Als Konvention werden Konstanten, die durch define definiert werden, üblichereise in Großbuchstaben geschrieben. Dies ist aber kein Muss. Die Möglichkeiten dieser Textersetzung gehen so weit, dass der Preprozessor auch in der Lage ist, Parameter zu erkennen und damit auch Makros definiert werden können. Als Beispiel lässt sich das Minimum zweier Zahlen auch als Makro über den Preprozessor lösen und ist damit unabhängig von dem tatsächlich verwendeten Datentyp:

#define max(x,y) ((x)>(y)?(x):(y))

Der Preprozessor erkennt x und y als Parameter. Im Ersatztext sollten diese Parameter geklammert werden, da es sich um Ausdrücke handeln und unter Umständen durch die Rangfolge der Operatoren ein ganz anderer Ausdruck als der beabsichtigte entstehen kann. Zur Verdeutlichung zeigen die folgenden Beispiele einmal den Quelltext vor und nach dem Preprozessorlauf.

#define max(x,y) ((x)>(y)?(x):(y))

int main(void)
   int x,y;

   y = 2;
   x = max(3,y+3);

Nach dem Preprozessorlauf sieht das wie folgt aus:

#define max(x,y) ((x)>(y)?(x):(y))

int main(void)
   int x,y;

   y = 2;
   x = ((3)>(y+3)?(3):(y+3));

#ifdef
#else
#endif

Mit #ifdef kann abgefragt werden, ob ein bestimmter Name per #define definiert worden ist. Wenn ja, wird der dahinter stehende Code bis zum #else oder #endif, wenn kein #else vorhanden ist, für den Compiler im Quelltext belassen. Der nicht zutreffende Teil ist für den Compiler nicht sichtbar. Er wird nicht in den Code übernommen, den der Compiler letztendlich auswertet. Es gibt auch die Abfrage, ob ein Name per #define nicht definiert ist. Hiermit können bestimmte Teile des Codes eingeschaltet werden, solange noch am Programm entwickelt wird und zusätzliche Ausgaben erwünscht sind. Dadurch, dass der Name nicht definiert wird, erzeugt man die Version, die ausgeliefert wird. Bei vielen Compilern ist es außerdem möglich, für solche Zwecke einen Namen beim Aufruf des Compilers zu definieren. Zur Verdeutlichung ein Beispiel:

int main(void)

   int x;

   x = 3;
#ifdef TEST
   x += 4;
#endif
   printf("x = %d", x);

und nun das gleiche Beispiel mit der Definition von TEST.

#define TEST

int main(void)

   int x;

   x = 3;
#ifdef TEST
   x += 4;
#endif
   printf("x = %d", x);

Es ist hier nicht notwendig, einen Wert für eine Textersetzung anzugeben, da nur die Existenz der Definition von TEST abgefragt wird. Es gibt keine Stelle, an der der Text TEST durch einen anderen Wert ersetzt werden soll. Die Ausgabe des Programms sollte die Wirkungsweise verdeutlichen.

#if
#elif
#else
#endif

Soll der Preprozessor auch einen Ausdruck auswerten, kann statt der ifdef-Anweisung die if-Anweisung verwendet werden. Hiermit kann z.B. auch der Wert eines mit define definierten Namen abgefragt werden. Um mehrere Bedingungen der Reihe nach abzufragen, gibt es die elif-Anweisung.

#if SYSTEM == SYSV
   #define HDR "sysv.h"
#elif SYSTEM == BSD
    #define HDR "bsd.h"
#elif SYSTEM == TOS
    #define HDR "tos.h"
#else
    #define HDR "default.h"
#endif

Und als ein weiteres Beispiel folgt die obige Funktion mit dem Flag TEST:

#define TEST 1
 
int main(void)

   int x;

   x = 3;
#if TEST == 1
   x += 4;
#endif
   printf("x = %d", x);

Das gleiche Beispiel wie oben prüft hier nicht die Existenz, sondern den Wert von TEST.

#pragma

Mit der Pragma-Anweisung können compilerspezifische Anweisungen ausgeführt werden. Dies könnten z.B. Einstellungen des Compilers sein.

defines für den Cookie-Jar

Mit dem Preprozessor lassen sich auch geeignete Konstanten für das Programm zum Auslesen des Cookie-Jars definieren. Weiterhin kann mit Preprozessor-Anweisungen dafür gesorgt werden, dass sich das Programm sowohl mit TC/PC, Sozobon als auch unter GCC compilieren lässt. Für das Auslesen der Adresse des Cookie-Jars muss mit der GEMDOS-Funktion Super der Prozessor in den Supervisor-Mode geschaltet werden. Damit der Compiler den Prototypen kennt, muss die Includedatei für OS-Aufrufe auf dem Atari eingebunden werden. Da rechnerspezifische Dinge nicht genormt sind, hat diese Datei in jeder der 3 Entwicklungsumgebungen einen anderen Namen. Da aber die Compiler ihren eigenen Namen als Konstanten definieren, der auch mit ifdef abgefragt werden kann, lässt sich je nach Compiler eine andere Includedatei einbinden.

Wenn __TURBOC__ definiert ist, wird TC als Compiler benutzt und der Name der Includedatei ist tos.h. Andernfalls wird geprüft, ob vielleicht __GNUC__ definiert ist. Wenn ja, ist der Compiler der gcc und die benötigte Datei heißt osbind.h. Wenn nicht, wird von Sozobon ausgegangen und als Includedatei tosbind.h benutzt. PC definiert sowohl __TURBOC__ zur Kompatibilität mit TC als auch __PUREC__.

#ifdef __TURBOC__
#include <tos.h>
#else
#ifdef __GNUC__
#include <osbind.h>
#else
#include <tosbind.h>
#endif
#endif
#include <stdio.h>

#define _p_cookies (void *)0x5a0l
#define NULL_COOKIE 0l

typedef struct cookie_entry {
   union {
      unsigned long name_long;
      char name_array[4];
   } name;
   unsigned long value;
} CookieEntry;

CookieEntry *GetCookieJar(void)
{  long OldStack;
   CookieEntry *CookieJar;

   OldStack = Super(0L);
   CookieJar = *((CookieEntry**)_p_cookies);
   Super((void *)OldStack);
   return CookieJar;
}

void PrintCookie(CookieEntry *Cookie)
{
   printf("Name des Cookies: %d\n", Cookie->name.name_long);
   printf("Wert des Cookies: %d\n", Cookie->value);
}

int IsNullCookie(CookieEntry *Cookie)
{
   return (Cookie->name.name_long == NULL_COOKIE);
}

void TraverseCookieJar(CookieEntry *Cookie)
{
   while (!IsNullCookie(Cookie))
   {
      PrintCookie(Cookie);
      Cookie++;
   }
}

int main(void)
{  CookieEntry *CookieJar;

   CookieJar = GetCookieJar();
   if (CookieJar == (CookieEntry *)0)
      printf("Dieses System hat keinen Cookie Jar\n");
   else
      TraverseCookieJar(CookieJar);
   return 0;
}

Modulares Kompilieren

Bisher wurde nach dem Compilieren das Programm schon mit Bibliotheken, also weiteren Funktionen, zu einem ausführbaren Programm gelinkt. Genauso lässt sich der Code des eigenen Programms auch auf mehrere Dateien aufteilen, die zusammengelinkt werden. Das Aufteilen auf mehrere Dateien bietet folgende Vorteile:

  • Die einzelnen Dateien sind kleiner und damit übersichtlicher.
  • Ein Programm lässt sich in Funktionsgruppen zerlegen und ist damit leichter verständlich.
  • Einzelne Teile lassen sich wiederverwenden.

Damit verbunden ist allerdings der höhere Verwaltungsaufwand, da mehrere Dateien übersetzt und gelinkt werden müssen. Diese Arbeit kann aber dem Computer überlassen werden, wenn das Tool make benutzt wird. TC bzw. PC benutzt Projektdateien. Dies ist eine einfachere Variante, die die Einbindung anderer Programme als die der Entwicklungsumgebung nicht erlaubt. Wir werden hier beide Möglichkeiten besprechen, die Aufteilung des Programms ist davon unberührt.

Was wäre eine mögliche Aufteilung des Cookie-Programms auf einzelne Dateien? Eine gute Möglichkeit ist es, wie bei der Suche nach Kandidaten für Objekte vorzugehen. Dazu sucht man nach Substantiven, die unsere Objekte bzw. Strukturen liefern. Die zu diesen Substantiven passenden Verben liefern die Methoden bzw. Funktionen. Bei der Vorstellung möglicher Module sollten möglichst viele Ideen über den Aufbau solcher Module vermittelt werden. Deshalb sind die Module nicht komplett nach den gleichen Ideen entworfen worden.

Zum einen haben wir den Cookie. Einen Cookie kann man auf einen Wert setzen, auf dem Monitor ausgeben, in eine Datei schreiben, einlesen, aus einer Datei einlesen oder vergleichen. Damit haben wir zwar mehr Funktionen, als hier benötigt. Wir haben aber auch schon genug Ideen für das nächste Kapitel über die ANSI-Bibliotheken. Das Modul enthält auch Funktionen bzw. Makros für das Setzen und Lesen der Komponenten der Struktur. Es ist auch möglich, direkt die Strukturkomponenten zu benutzen. Die Verwendung von entsprechenden Methoden erlaubt es, den Aufbau der Struktur (z.B. für eine Verbesserung) zu ändern, ohne dass sich der Zugriff ändert. Werden Objekte deklariert, kann sogar der Zugriff auf diese Komponenten verhindert werden. Das nennt sich data hiding: der Anwender bekommt nur Zugriff über eine definierte Schnittstelle. Eine Initialisierung des Cookie-Entrys ist nicht erforderlich, weil es keine sinnvolle Vorbesetzung gibt oder der Cookie sowieso aus dem Cookie-Jar stammt. Neue Cookies müssen sowieso komplett auf ihren Wert gesetzt werden.

Dann haben wir den Cookie-Jar. Zuerst muss der Cookie-Jar ermittelt werden, wir können nach bestimmten Cookies suchen und den Cookie-Jar durchwandern.

Der Linker kann nur gesamte Objektdateien zu einem Programm zu linken und keine einzelnen Funktionen aus einem Modul herausholen. Deshalb wird üblicherweise jede Funktion in eine Einzelne Datei geschrieben und die daraus resultierenden Objektdateien zu einer Bibliothek gelinkt. Wir begnügen uns hier allerdings mit einer Objektdatei.

Die einzelnen Module werden nun der Reihe nach vorgestellt. Funktionen, für die noch zusätzliches Wissen erforderlich ist, werden zunächst ohne Funktionalität implementiert.

Damit die für ein Modul definierten Datentypen auch in anderen Modulen verwendet werden können und der Compiler die Parameter eines Funktionsaufrufs auf Korrektheit prüfen kann, schreiben wir eine Headerdatei. Die Headerdatei fragt zuerst ab, ob wir einen bestimmten Namen definiert haben. Wenn ja, ist unsere Includedatei schon bekannt und wir müssen unsere Definitionen verstecken, um Fehlermeldungen oder Warnungen es Compilers zu vermeiden. Wenn nein, definieren wir zuerst den Namen und anschließend alles, was unser Modul anderen zur Verfügung stellt.

cookie.h

Diese Headerdatei zeigt, wie eine Mehrfachincludierung zustande kommen könnte. In den Funktionsprototypen wird die Struktur FILE benötigt. Damit ein Benutzer unseres Moduls nicht wissen muss, was er noch includieren muss, tun wir dies selbst. Deshalb wird die stdio.h includiert. Da wir in cookie.c aber Ein- und Ausgabe machen wollen, benötigen wir die Definitionen aus stdio.h dort auch. Um nicht wissen zu müssen, ob diese Includedatei indirekt benutzt wird, wird sie auch dort includiert. Dadurch würden die Definitionen zweimal gemacht. Dies wird von der Abfrage, ob COOKIE_H definiert ist und der anschließenden Definition verhindert.

#ifndef COOKIE_H

#define COOKIE_H

#include <stdio.h>

#define NULL_COOKIE 0l

typedef struct cookie_entry {
   union {
      unsigned long name_long;
      char name_array[4];
   } name;
   unsigned long value;
} CookieEntry;

#define CookieGetValue(x) (x)->value
#define CookieGetNameL(x) (x)->name.name_long
#define CookieGetNameS(x) (x)->name.name_array

void CookieSetL(CookieEntry *Cookie,long Name,long Value);
void CookieSetS(CookieEntry *Cookie,char *Name,long Value);
void CookiePrint(CookieEntry *Cookie);
void CookieInput(CookieEntry *Cookie);
int CookieIsNullCookie(CookieEntry *Cookie);
int CookieIsCookie(CookieEntry *Cookie,long Name);
int CookieRead(CookieEntry *Cookie,FILE *stream);
int CookieWrite(CookieEntry *Cookie,FILE *stream);

#endif

cookie.c

Und nun noch die dazugehörige C-Datei. Damit der Compiler prüfen kann, ob die Funktionen auch den in der Includedatei angegebenen Prototypen entsprechen, wird die Includedatei auch hier includiert. Da dieses Modul allein kein ablauffähiges Programm ergeben soll, sondern nur Funktionalität zur Verfügung stellt, fehlt hier die Funktion main.

Einige Funktionen sind noch leer bzw. enthalten nur ein return mit einem entsprechenden Returnwert. Diese Funktionen werden später bei der Besprechung der ANSI-Libs ausgefüllt, wenn das nötige Wissen zur Verfügung steht.

#include <string.h>
#include <stdio.h>
#include "cookie.h"

void CookieSetL(CookieEntry *Cookie,long Name,long Value)
{
   Cookie->name.name_long = Name;
   Cookie->value = Value;
}

void CookieSetS(CookieEntry *Cookie,char *Name,long Value)
{
   memcpy(Cookie->name.name_array,Name,4);
   Cookie->value = Value;
}

void CookiePrint(CookieEntry *Cookie)
{
   printf("Name des Cookies: %d\n", Cookie->name.name_long);
   printf("Wert des Cookies: %d\n", Cookie->value);
}

void CookieInput(CookieEntry *Cookie)
{
}

int CookieIsNullCookie(CookieEntry *Cookie)
{
   return Cookie->name.name_long == NULL_COOKIE;
}

int CookieIsCookie(CookieEntry *Cookie,long Name)
{
   return Cookie->name.name_long == Name;
}

int CookieRead(CookieEntry *Cookie,FILE *stream)
{
   return 0;
}

int CookieWrite(CookieEntry *Cookie,FILE *stream)
{
   return 0;
}

cjar.h

Dieses Modul enthält eine Funktion zur Initialisierung, die den Cookie-Jar ermittelt. Auch hier wird das Prinzip des data hiding angewandt und der Cookie-Jar nicht zurückgeliefert. Stattdessen wird nur ein Wert von 0 geliefert, wenn kein Cookie-Jar existiert und ein Wert ungleich 0, wenn der Cookie-Jar ermittelt wurde.

Achtung: Das Modul geht davon aus, dass der Cookie-Jar während der Laufzeit des Programms nicht mehr verändert wird. Dies ist, insbesondere unter einem Multitasking-OS, nicht garantiert. Für diesen Kurs ist diese Vereinfachung allerdings zulässig, da in der Regel der Cookie-Jar von Programmen nicht verändert wird.

#ifndef CJAR_H

#define CJAR_H

#include cookie.h

typedef void (*CookieAction)(CookieEntry *Cookie);

int CjarInit(void);
CookieEntry *CjarSearchL(long Name);
CookieEntry *CjarSearchS(char *Name);
void CjarTraverse(CookieAction ActionFkt);

#endif

cjar.c

Die Implementierung zeigt auch, wie ein Modul automatisch initialisiert werden kann. Dazu wird einfach der Zeiger auf den Cookie Jar mit einem entsprechenden Wert initialisiert. Dies ist NULL für einen ungültigen Zeiger. Bei jedem Zugriff kann die Initialisierung aufgerufen und geprüft werden, ob dieser Zeiger noch ein ungültiger Zeiger ist.

Weiterhin nutzt diese Modul auch Funktionspointer für das Durchwandern des Cookie-Jars. Damit kann eine Funktion, die etwas mit einem Cookie tut, an anderer Stelle programmiert werden. Dieses Modul weiß, wie man den Cookie-Jar durchläuft und ruft für jeden Cookie die Funktion auf, die etwas mit einem einzelnen Cookie tut.

#ifdef __TURBOC__
#include <tos.h>
#else
#ifdef __GNUC__
#include <osbind.h>
#else
#include <tosbind.h>
#endif
#endif

#include <stddef.h>
#include cookie.h
#include cjar.h

#define _p_cookies (void *)0x5a0l

CookieEntry *CookieJar = NULL;

int CjarInit(void)
{  long OldStack;

   OldStack = Super(0L);
   CookieJar = *((CookieEntry**)_p_cookies);
   Super((void *)OldStack);
   return (CookieJar != NULL);
}

CookieEntry *CjarSearchL(long Name)
{
   return NULL;
}

CookieEntry *CjarSearchS(char *Name)
{
   return NULL;
}

void CjarTraverse(CookieAction ActionFkt)
{  CookieEntry *AktCookie;

   if (CookieJar != NULL)
      CjarInit();
   if (CookieJar != NULL)
   {
      AktCookie = CookieJar;
      while (!CookieIsNullCookie(AktCookie))
      {
         (*ActionFkt)(AktCookie);
         AktCookie++;
      }
   }
}

cmain.c

Die main-Funktion kann mit solchen umfangreichen Modulen sehr einfach gehalten werden. Es muss lediglich cjar initialisiert werden. Anschließend kann mit CjarTraverse der Cookie-Jar durchwandert werden. Die übergebene Funktion ist einfach die Ausgabefunktion aus dem Modul cookie.

#include <stdio.h>
#include "cookie.h"
#include "cjar.h"

int main(void)
{
   if (CjarInit())
      CjarTraverse(CookiePrint);
   else
      printf("Dieses System hat keinen Cookie Jar\n");
   return 0;
}

cookie.prj

TC/PC bietet mit den Projektdateien eine Möglichkeit, automatisch von der Entwicklungsumgebung entscheiden zu lassen, welche Dateien übersetzt werden müssen. Die Projektdatei gibt außerdem an, welche Dateien zusammen das Programm bilden. Das Beispiel unten ist die Projektdatei für TC, für PC muss nur in den Angaben für *.LIB und *.O das TC gegen PC getauscht werden.

In der ersten Zeile steht der Name des Programms, das das Ergebnis bildet. Nach dem Gleichheitszeichen folgen sämtliche Dateien, die das Projekt bilden. Die erste dieser Dateien ist immer der Startup-Code. Dies ist der erste Code, der nach dem Start des Programms ausgeführt wird. Dieser Code ruft die Funktion main auf. Anschließend folgen unsere C-Dateien und zum Schluss die benötigten Bibliotheken.

Wenn TC/PC sieht, dass der angegebene Quellcode neuer ist als die daraus übersetzte Objektdatei, wird diese Datei neu compiliert und das Projekt gelinkt. Es ist zusätzlich möglich, in Klammern Dateien anzugeben, von denen eine Datei abhängt. In cmain.c werden cjar.h und cookie.h includiert, also werden diese Dateien noch mit angegeben. Wenn sich cjar.h oder cookie.h ändern, also damit neuer sind als cmain.c, so wird cmain.c neu compiliert. Auf diese Weise kann der Compiler prüfen, ob die Definitionen aus den Includedateien noch richtig benutzt werden.

Eine Abhängigkeit von den C-Dateien (cjar.c und cookie.c) ist nicht vorhanden. Wenn nur die C-Dateien ohne die Includedateien geändert werden, dann hat sich die Schnittstelle, also die Datentypen und die Funktionen, nicht verändert. Wie eine Funktion intern arbeitet, ist aber für den Aufruf der Funktion nicht wichtig.

COOKIE.TOS
=
TCSTART.O
CMAIN.C (CJAR.H, COOKIE.H)
CJAR.C (CJAR.H, COOKIE.H)
COOKIE.C (COOKIE.H)
TCSTDLIB.LIB
TCTOSLIB.LIB

makefile

Für Sozobon und GCC kann das Tool make benutzt werden. Da make per default nach der Datei makefile sucht, bekommt unser Makefile auch diesen Namen.

In den ersten zwei Zeilen wird ein Makro definiert. Die Zeile mit dem Definition, also wird die erste Zeile auskommentiert. Für Sozobon ist die erste Definition zuständig. Dieses Makro legt fest, welches Programm für das Übersetzen aufzurufen ist. Auch für das Linken des Programms wird cc bzw. gcc aufgerufen. Dieses Programm erkennt anhand der Parameter selbständig, ob der eigentliche Compiler oder der Linker zu starten ist.

In einem Makefile gibt man eine Datei an, gefolgt von einem Doppelpunkt und den Dateien, von dem diese Datei abhängt. Darunter steht die Aktion, die ausgeführt werden soll. Achtung: jede Aktion beginnt mit einem Tabulator! Wenn der Editor stattdessen Leerzeichen einfügt, funktioniert die Makedatei nicht!

Make sucht nach der ersten Regel und versucht, sie auszuführen. Das wäre das komplette Programm, das natürlich von den Objektdateien aus unseren Quellen abhängt. Sind die Objekte neuer, wird die Aktion ausgeführt und das Programm neu gelinkt. Da für die Objekte auch wieder entsprechende Regeln definiert sind, werden auch diese überprüft. Eine Objektdatei ist natürlich von der Quelldatei und auch von den benutzen Includedateien abhängig. Immer dann, wenn sich die Schnittstelle zu dem Modul, also die Datentypen und Funktionen ändern, ändert sich die Includedatei. Damit ist diese neuer als die Quelle und die Quelle wird neu übersetzt. So wird garantiert, dass unsere veränderten Definitionen benutzt werden.

Da in einem Makefile die Aktion explizit angegeben wird, bietet es noch mehr Möglichkeiten als die Projektdatei von TC/PC. Es lassen sich beliebige Abhängigkeiten und dazu beliebige Aktionen definieren.

CC=	gcc

cookie.tos:	cmain.o cjar.o cookie.o
	 cmain.o cjar.o cookie.o -o cookie.tos

cmain.o: cmain.c
	 -c cmain.c

cjar.o: cjar.c
	 -c cjar.c

cookie.o: cookie.c
	 -c cookie.c

Der nächste Teil gibt einen Einblick in die ANSI-Bibliotheken

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil X

Die ANSI-Bibliotheken

Zu einem C-Compiler gehören auch noch umfangreiche Bibliotheken, die ebenfalls nach ANSI genormt sind. Im Folgenden werden diese Bibliotheken kurz vorgestellt. Die Reihenfolge orientiert sich an den Include-Dateien, in denen die entsprechenden Prototypen zu finden sind. Diese Beschreibung ersetzt keine ausführliche Beschreibung eines Lehrbuchs, sondern soll nur einen Überblick darüber geben, was mit ANSI C mitgeliefert wird.

stdio.h

Die stdio.h enthält Funktionen für Ein- und Ausgabe. Das sind zum einen Funktionen, die auf einen Datenstrom (stream) arbeiten, der einer Datei oder einem Gerät zugeordnet werden kann. Diese Funktionen haben einen Parameter vom Typ FILE *, der den stream beschreibt. Die Struktur FILE soll von einem Benutzer nicht verändert werden! Ein stream muss vor seiner Verwendung geöffnet (fopen) und nach seiner Verwendung geschlossen (fclose) werden. Die Daten eines streams werden gepuffert, so dass nicht für jedes Byte ein Zugriff auf die Platte nötig ist. Zusätzlich gibt es noch Funktionen, die auf den Standardkanälen für Ein- und Ausgabe arbeiten. Dies sind die streams stdin, stdout und stderr, die schon ab Programmstart offen sind.

void clearerr( FILE *stream );
Diese Funktion löscht intern gespeicherte Informationen über den zuletzt aufgetretenen Fehler bei Verwendung von stream.
int fclose( FILE *stream );
Diese Funktion schließt einen Stream. Damit werden auch sämtliche Daten im Puffer geschrieben.
int feof( FILE *stream );
Diese Funktion prüft, ob das Dateiende erreicht ist. Ein Wert ungleich Null bedeutet, dass das Dateiende erreicht ist.
int ferror( FILE *stream );
Diese Funktion prüft, ob ein Fehler bei der Benutzung von stream aufgetreten ist. Ein Wert ungleich Null bedeutet, dass ein Fehler aufgetreten ist.
int fflush( FILE *stream );
Diese Funktion schreibt die noch im Puffer befindlichen Daten in den stream.
int fgetc( FILE *stream );
Diese Funktion liest ein Zeichen (unsigned char) aus dem Stream. Wenn kein Zeichen zur Verfügung steht, wird EOG geliefert.
int getc( FILE *stream );
Diese Funktion ist äquivalent zu fgetc, kann aber als Makro definiert sein.
int getchar( void );
Diese Funktion ist äquivalent zu getc(stdin).
int fgetpos( FILE *stream, fpos_t *pos );
Diese Funktion speichert die momentane Position innerhalb der Datei in die Variable pos, um sie z.B. mittels fsetpos später wieder anzuspringen.
char *fgets( char *str, int n, FILE *stream );
Diese Funktion liest eine Zeile in die Variable str. Der Parameter n gibt an, wieviel Zeichen inklusive der abschließenden Null str aufnehmen kann.
FILE *fopen( const char *filename, const char *mode );
Diese Funktion öffnet eine Datei und ordnet sie einem stream zu. Da unter UNIX und MiNT die Geräte bzw. Schnittstellen eine Repräsentation im Dateisystem haben, lassen sich hier auch streams für Schnittstellen öffnen. Unter MiNT sind dies die Dateien im Verzeichnis u:\dev. Wenn die Datei nicht existiert, wird NULL zurückgegeben. Der Parameter mode gibt den Zugriffsmodus an:
"w"
Die Datei wird zum Schreiben geöffnet. Wenn die Datei nicht existiert, wird sie angelegt. Existiert die Datei, wird sie gelöscht.
"r"
Die Datei wird zum Lesen geöffnet. Die Datei muss existieren.
"w+"
Die Datei wird zum Schreiben und Lesen geöffnet. Wenn die Datei nicht existiert, wird sie angelegt. Existiert die Datei, wird sie gelöscht. Wird zwischen Lesen und Schreiben gewechselt, muss entweder fflush aufgerufen oder der Dateizeiger mittels fseek neu positioniert werden.
"r+"
Die Datei wird zum Lesen und zum Schreiben geöffnet. Die Datei muss existieren. Wird zwischen Lesen und Schreiben gewechselt, muss entweder fflush aufgerufen oder der Dateizeiger mittels fseek neu positioniert werden.
"a"
Die Datei wird zum Anhängen geöffnet. Die Datei muss existieren.
Enthält der Mode nach dem ersten Zeichen noch ein b, z.B. "rb" oder "w+b", wird die Datei im Binärmodus geöffnet. Ein Öffnen im Textmodus bedeutet auf Ataris oder PCs auch, dass ein Linefeed '\n' im Stream zu einem CR/LF in der Datei gewandelt wird. Beim Einlesen wird entsprechend ein Return gefolgt von einem Linefeed zu einem Linefeed. Damit wird in der Datei oder bei der Bildschirmausgabe das Zeilenende benutzt, dass das System benutzt. In der Zeichenkette im C-Programm wird immer '\n' als Zeilenende benutzt.
int fprintf( FILE *stream, const char *format, ... );
Diese Funktion schreibt beliebige Parameter anhand der Informationen, die der Formatstring format vorgibt. Nach dem Formatstring folgen die Variablen oder Konstanten, die ausgegeben werden sollen.
int fputc( int ch, FILE *stream );
Diese Funktion schreibt ein Zeichen (unsigned char).
int putc( int ch, FILE *stream );
Diese Funktion ist äquivalent zu fputc, kann aber als Makro definiert sein.
int putchar( int c );
Diese Funktion ist äquivalent zu putc( c, stdout);
int fputs( const char *str, FILE *stream );
Diese Funktion schreibt eine Zeichenkette.
size_t fread( void *buf, size_t elem_Siz, size_t count, FILE *stream );
Diese Funktion liest count Elemente der Größe elem_Siz in den Puffer buf ein. Die Funktion gibt die Anzahl der gelesenen Elemente zurück.
FILE *freopen( const char *filename, const char *mode, FILE *stream );
Diese Funktion öffnet eine Datei und ordnet sie einem vorhandenen Stream zu. Damit lassen sich z.B. die Dateien ändern, die stdin, stdout und stderr zugeordnet sind.
int fscanf( FILE *stream, const char *format, ... );
Diese Funktion liest Daten ein; das Format der Daten wird durch den Formatstring format vorgegeben. Anschließend folgen die Adressen der Variablen, in die eingelesen werden soll.
int fseek( FILE *stream, long offset, int mode );
Diese Funktion legt fest, ab welcher Position in der Datei weitere Ein- und Ausgaben erfolgen. Es wird also der Dateizeiger neu positioniert. Der Parameter mode gibt an, bezüglich welcher Stelle in der Datei neu positioniert werden soll:
SEEK_SET
Positioniert relativ zum Dateianfang.
SEEK_CUR
Positioniert relativ zur aktuellen Position.
SEEK_END
Positioniert relativ zum Dateiende.
void rewind( FILE *stream);
Diese Funktion ist äquivalent zu
fseek(strem,0,SEEK_SET); clearerr(stream);
int fsetpos( FILE *stream, const fpos_t *pos );
Diese Funktion setzt den Dateizeiger wieder an die Stelle, die mittels fgetpos gespeichert wurde.
long ftell( FILE *stream );
Diese Funktion liefert die Position innerhalb der Datei.
size_t fwrite( const void *buf, size_t elem_Siz, size_t count, FILE *stream );
Diese Funktion schreibt count Elemente der Größe elem_Siz aus dem Puffer buf. Die Funktion gibt die Anzahl der geschriebenen Elemente zurück.
char *gets( char *str );
Diese Funktion liest die nächste Zeile von der Standardeingabe ein.
void perror( char *s );
Diese Funktion gibt zuerst s und dann eine Fehlermeldung passend zu dem aktuellen Wert der Variable errno aus. Der Text ist von der Implementierung abhängig.
int printf( const char *format, ... );
Diese Funktion verhält sich wie fprintf, gibt aber auf die Standardausgabe aus.
int puts( const char *str );
Diese Funktion schreibt eine Zeichenkette auf die Standardausgabe.
int scanf( const char *format, ... );
Diese Funktion verhält sich wie fscanf, liest aber von der Standardeingabe.
void setbuf( FILE *stream, char *buf );
Wird für buf der Wert NULL übergeben, wird für den Stream die Pufferung ausgeschaltet. Für einen Wert ungleich NULL ist die Funktion äquivalent zu setvbuf(stream, buf, _IOFBF, BUFSIZ);
int setvbuf( FILE *stream, char *buf, int type, size_t size );
Diese Funktion legt die Pufferung fest und muss vor allen anderen Operationen auf diesen Stream aufgerufen werden. Hat buf den Wert NULL, wird ein eigener Puffer angelegt, ansonsten wird buf verwendet. Der Wert size gibt die Puffergröße an. Der Wert type gibt die Art der Pufferung an:
_IOFBF
Die Datei wird vollständig gepuffert.
_IOLBF
Die Textdatei wird zeilenweise gepuffert.
_IONBF
Die Datei wird nicht gepuffert.
int sprintf( char *string, const char *format, ... );
Diese Funktion verhält sich wie fprintf, gibt aber in eine Zeichenkette aus.
int sscanf( char *string, const char *format, ... );
Diese Funktion verhält sich wie fscanf, liest aber aus einer Zeichenkette.
char *tmpnam( char *s );
Wird NULL übergeben, erzeugt diese Funktion einen Namen, der als Dateiname für eine temporäre Datei verwendet werden kann. Es ist sichergestellt, dass noch keine Datei mit diesen Namen existiert. Wird ein Name, der mindestens L_tmpnam Zeichen enthält, übergeben, wird dieser gespeichert und als Ergebnis geliefert.
FILE *tmpfile( void );
Diese Funktion erzeugt eine temporäre Datei, die mit dem Mode "wb+" geöffnet wurde. Konnte keine Datei geöffnet werden, ist das Ergebnis NULL.
int ungetc( int ch, FILE *stream );
Diese Funktion stellt ein gelesenes Zeichen wieder in den Stream zurück. Ein erneutes Lesen mittels fgetc( stream) würde wieder dieses Zeichen lesen.
int vfprintf( FILE *stream, const char *format, va_list param );
Diese Funktion ist äquivalent zu fprintf, benutzt aber keine variable Parameterliste. Der Parameter ist ein Wert, der durch va_start und vielleicht va_arg initialisiert werden kann. Damit kann eine variable Parameterliste an vfprintf weitergereicht werden.
int vprintf( const char *format, va_list param );
Diese Funktion ist äquivalent zu printf, benutzt aber keine variable Parameterliste. Der Parameter ist ein Wert, der durch va_start und vielleicht va_arg initialisiert werden kann. Damit kann eine variable Parameterliste an vprintf weitergereicht werden.
int vsprintf( char *string, const char *format, va_list param );
Diese Funktion ist äquivalent zu sprintf, benutzt aber keine variable Parameterliste. Der Parameter ist ein Wert, der durch va_start und vielleicht va_arg initialisiert werden kann. Damit kann eine variable Parameterliste an vsprintf weitergereicht werden.
int vfscanf( FILE *stream, const char *format, va_list param );
Diese Funktion ist äquivalent zu fscanf, benutzt aber keine variable Parameterliste. Der Parameter ist ein Wert, der durch va_start und vielleicht va_arg initialisiert werden kann. Damit kann eine variable Parameterliste an vfscanf weitergereicht werden.
int vscanf( const char *format, va_list param );
Diese Funktion ist äquivalent zu scanf, benutzt aber keine variable Parameterliste. Der Parameter ist ein Wert, der durch va_start und vielleicht va_arg initialisiert werden kann. Damit kann eine variable Parameterliste an vscanf weitergereicht werden.
int vsscanf( char *string, const char *format, va_list param );
Diese Funktion ist äquivalent zu sscanf, benutzt aber keine variable Parameterliste. Der Parameter ist ein Wert, der durch va_start und vielleicht va_arg initialisiert werden kann. Damit kann eine variable Parameterliste an vsscanf weitergereicht werden.
int fileno( FILE *stream );
Diese Funktion liefert das Handle der Funktion, also unter TOS das GEMDOS-Dateihandle. Achtung: diese Funktion darf nicht dazu benutzt werden, unterschiedliche Schichten (Stream, GEMDOS) zu mischen! Dies würde zu Fehlern führen!

Der nächste Teil beschäftigt sich mit dem Formatstring, mit dessen Hilfe komfortable Ein- und Ausgabe-Operationen (scanf bzw. printf) durchgeführt werden können.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil XI

Formatstring

Der Formatstring enthält einen Text, der ausgegeben bzw. genau so in der Eingabe vorhanden sein muss. In diesem Text können Platzhalter angegeben werden, an deren Stelle Variablen ausgegeben bzw. eingelesen werden. Jeder dieser Platzhalter beginnt mit dem Zeichen '%' und endet mit einem Zeichen, das den Datentyp kodiert. Zwischen dem Prozentzeichen und dem Typ können noch folgende Angaben stehen: Flags, die das Ausgabeformat genauer spezifizieren, einer Ausgabebreite, einer Genauigkeit und einem Flag h oder l für eine Größenangabe. Für jeden Platzhalter muss in der variablen Parameterliste auch eine entsprechende Variable folgen!

Hier noch einmal die Kurzzusammenfassung eines Platzhalters:

 % [Flags] [Breite] [.Präzision] [h|l|L] Typ

Als Flags sind folgende Zeichen möglich: Minus '-', Plus '+', Doppelkreuz '#' und Leerzeichen ' '. Die Flags können in beliebiger Folge und Kombination erscheinen und haben die folgenden Bedeutung:

-
Die Variable wird linksbündig ausgegeben. Sind Leerzeichen für die gewünschte Breite nötig, werden sie rechts eingefügt. Ohne dieses Flag arbeitet printf immer rechtsbündig.
+
Eine numerische Ausgabe wird mit einem Vorzeichen gemacht. Ohne dieses Flag werden nur negative Werte mit einem Vorzeichen versehen.
' ' (Leerzeichen)
Positive Werte werden mit einem führenden Leerzeichen ausgegeben. Ohne dieses Flag stellt printf positiven Werten überhaupt nichts voran.
#
Die Konvertierung des Arguments erfolgt in alternativer Form. Die Tabelle gibt an, für welche Typen welche Wirkungen eintritt:
c,s,d,i,u (keine)
o Voranstellen von '0'
x,X Voranstellen von "0x" bzw. "0X"
e,E,f es wird immer ein Dezimalpunkt ausgegeben
g,G wie e bzw. E und folgende Nullen werden nicht unterdrückt

Die Breite legt die minimale Zahl auszugebender Zeichen fest. Sie kann in zwei Formen erfolgen, entweder direkt als Zahl innerhalb des Formatstrings oder über ein Sternchen '*'. Bei der Verwendung von '*' erwartet printf einen zusätzlichen Parameter, der in der Liste direkt vor dem auszugebenden Wert stehen und den Typ int haben muss. Der Breite als Zahlkonstante kann die Ziffer 0 vorangestellt werden, dann wird mit der Ziffer '0' und nicht mit Leerzeichen aufgefüllt. Erzeugt die Ausgabe weniger Zeichen, als die Breite angibt, wird mit Leerzeichen aufgefüllt. Erzeugt die Ausgabe mehr Zeichen, wird die Breite ignoriert. Es werden keine Zeichen abgeschnitten.

Die Präzision wird immer mit einem Dezimalpunkt '.' eingeleitet und gibt die maximale Anzahl Zeichen an, die ausgegeben werden soll. Auch hier kann anstelle der Konstanten der Stern '*' angegeben werden, wenn die Angabe über einen zusätzlichen Parameter erfolgt.

Die Buchstaben 'h', 'l' oder 'L' geben an, ob es sich bei dem Parameter um einen short, long oder long double handelt.

Der Typ kann einen der folgenden Werte annehmen:

d Integer signed int (dezimal);
i Integer signed int (dezimal);
o Integer unsigned int (oktal);
u Integer unsigned int (dezimal);
x Integer unsigned int (hexadezimal), Buchstaben a..f;
X Integer unsigned int (hexadezimal), Buchstaben A..F.
f Fließkomma vorzeichenbehafteter Wert der Form [-]dddd.dddd
e Fließkomma vorzeichenbehafteter Wert der Form [-]d.dddd e [+|-]ddd
g Fließkomma vorzeichenbehafteter Wert im e- oder f-Format
E Fließkomma wie e, aber mit dem Zeichen E vor dem Exponenten
G Fließkomma wie g, aber mit dem Zeichen E vor dem Exponenten
c Zeichen einzelnes Zeichen
s String Nullterminierte Zeichenkette
% (nichts) Ausgabe des Zeichens %
n *int speichert die Anzahl der bis jetzt ausgegebenen Zeichen
p Zeiger gibt den Parameter als Zeiger in hexadezimaler Form aus.

Ein-/Ausgabe in cookie.c

Mit dem Wissen über die stdio können nun die Funktionen für einen Cookie vervollständigt werden. Es fehlten ja noch die Eingabefunktion und die Funktionen zum Schreiben in Dateien und Lesen aus Dateien.

#include <string.h>
#include <stdio.h>
#include "cookie.h"

void CookieSetL(CookieEntry *Cookie,long Name,long Value)
{
   Cookie->name.name_long = Name;
   Cookie->value = Value;
}

void CookieSetS(CookieEntry *Cookie,char *Name,long Value)
{
   memcpy(Cookie->name.name_array,Name,4);
   Cookie->value = Value;
}

void CookiePrint(CookieEntry *Cookie)
{
   printf("Name des Cookies: %d\n", Cookie->name.name_long);
   printf("Wert des Cookies: %d\n", Cookie->value);
}

void CookieInput(CookieEntry *Cookie)
{
   printf("Name des Cookies (long): ");
   scanf("%ld\n", &(Cookie->name.name_long));
   printf("Wert des Cookies (long): ");
   scanf("%ld\n", &(Cookie->value));
}

int CookieIsNullCookie(CookieEntry *Cookie)
{
   return Cookie->name.name_long == NULL_COOKIE;
}

int CookieIsCookie(CookieEntry *Cookie,long Name)
{
   return Cookie->name.name_long == Name;
}

int CookieRead(CookieEntry *Cookie,FILE *stream)
{
   return(fread(Cookie,sizeof(CookieEntry),1,stream) == 1);
}

int CookieWrite(CookieEntry *Cookie,FILE *stream)
{
   return(fwrite(Cookie,sizeof(CookieEntry),1,stream) == 1);
}

ctype.h

Die ctype.h definiert Funktionen zum Testen von Zeichen. Die Prüfroutinen geben einen Wert ungleich 0 zurück, wenn der Test erfolgreich war. Die Funktionen sind nur für 7-Bit ASCII definiert, d. h., die Umlaute sind keine Buchstaben! Ein Kleinbuchstabe umfasst nur den Bereich von 'a' .. 'z'.

int isalnum( int c );
Diese Funktion entspricht isalpha(c) || isdigit(c)
int isalpha( int c );
Diese Funktion entspricht islower(c) || isupper(c)
int iscntrl( int c );
Ist das Zeichen ein Steuerzeichen, also kleiner als das Leerzeichen ' '?
int isdigit( int c );
Ist das Zeichen eine Ziffer?
int isgraph( int c );
Ist das Zeichen ein sichtbares Zeichen? Also auch kein Leerzeichen.
int isprint( int c );
Ist das Zeichen darstellbar? Also keine Steuerzeichen, aber es kann ein Leerzeichen sein.
int ispunct( int c );
Ist das Zeichen ein darstellbares Zeichen mit Ausnahme von Buchstaben, Ziffer und Leerzeichen?
int islower( int c );
Ist das Zeichen ein Kleinbuchstabe?
int isupper( int c );
Ist das Zeichen ein Großbuchstabe?
int ixdigit( int c );
Ist das Zeichen eine hexadezimale Ziffer?
int isspace( int c );
Ist das Zeichen ein Leerzeichen, Seitenvorschub '\f', Zeilentrenner (Linefeed) '\n', Wagenrücklauf (Return) '\r', Tabulator '\t' oder ein Vertikaltabulator '\v'? Diese Zeichen nennt man auch white space, da sie die Cursorposition auf dem Bildschirm verändern, aber selbst keine Ausgabe produzieren.
int tolower( int c );
Diese Funktion wandelt c in einen Kleinbuchstaben.
int toupper( int c );
Diese Funktion wandelt c in einen Großbuchstaben.

Testfunktionen in cookie.c

Mit den Testfunktionen lässt sich nun prüfen, ob der Name des Cookies nur aus darstellbaren Zeichen ohne Leerzeichen besteht. Wenn ja, könnte es sich um ein Kürzel handeln und der Name des Cookies lässt sich auch als Text ausgeben. Da im Cookie selbst der Platz für die abschließende Null fehlt, wird die Textausgabe im nachfolgenden Kapitel über string.h besprochen.

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "cookie.h"

void CookieSetL(CookieEntry *Cookie,long Name,long Value)
{
   Cookie->name.name_long = Name;
   Cookie->value = Value;
}

void CookieSetS(CookieEntry *Cookie,char *Name,long Value)
{
   memcpy(Cookie->name.name_array,Name,4);
   Cookie->value = Value;
}

void CookiePrint(CookieEntry *Cookie)
{
   printf("Name des Cookies: %d\n", Cookie->name.name_long);
   printf("Wert des Cookies: %d\n", Cookie->value);
   if (isgraph(Cookie->name.name_array[0]) &&
       isgraph(Cookie->name.name_array[1]) &&
       isgraph(Cookie->name.name_array[2]) &&
       isgraph(Cookie->name.name_array[3]))
    {
       printf("Der Cookie besteht aus darstellbaren Zeichen\n");
    }
}

void CookieInput(CookieEntry *Cookie)
{
   printf("Name des Cookies (long): ");
   scanf("%ld\n", &(Cookie->name.name_long));
   printf("Wert des Cookies (long): ");
   scanf("%ld\n", &(Cookie->value));
}

int CookieIsNullCookie(CookieEntry *Cookie)
{
   return Cookie->name.name_long == NULL_COOKIE;
}

int CookieIsCookie(CookieEntry *Cookie,long Name)
{
   return Cookie->name.name_long == Name;
}

int CookieRead(CookieEntry *Cookie,FILE *stream)
{
   return(fread(Cookie,sizeof(CookieEntry),1,stream) == 1);
}

int CookieWrite(CookieEntry *Cookie,FILE *stream)
{
   return(fwrite(Cookie,sizeof(CookieEntry),1,stream) == 1);
}

string.h

In string.h sind Funktionen für die Arbeit mit Zeichenketten (Strings) und Speicherblöcken definiert. Wenn nichts anderes beschrieben ist, muss für jede dieser Funktionen der Speicher für das Ziel vom Aufrufer angelegt werden! Dabei muss auch der Platz für die Null als String-Ende berücksichtigt werden. Wenn nichts anderes beschrieben ist, liefern die Funktionen das Ziel als Ergebnis. Die mem...-Funktionen operieren auf einem beliebigen Speicherblock.

void richtig(void)
{  char s[6];

   strcpy(s,"Hallo");
}

void falsch(void)
{  char *s;

   strcpy(s,"Hallo");
}
char *strcat( char *s1, const char *s2 );
Diese Funktion hängt den String s2 an den String s1 an.
char *strncat( char *s1, const char *s2, size_t n );
Diese Funktion hängt maximal n Zeichen des Strings s2 an den String s1.
int strcmp( const char *s1, const char *s2 );
Diese Funktion vergleicht beide Strings. Ist s1<s2, liefert sie einen Wert < 0, bei s2 > s1 einen Wert > 0 und für Gleichheit 0.
int strncmp( const char *s1, const char *s2, size_t n );
Diese Funktion vergleicht maximal n Zeichen aus beiden Strings. Ist s1<s2 für die ersten n Zeichen, liefert sie einen Wert < 0, bei s2 > s1 einen Wert > 0 und für Gleichheit 0.
char *strcpy( char *s1, const char *s2 );
Diese Funktion kopiert den String s2 nach s1.
char *strncpy( char *s1, const char *s2, size_t n );
Diese Funktion kopiert maximal n Zeichen von s2 nach s1.
size_t strlen( const char *s );
Diese Funktion liefert die Länge von s.
char *strchr( const char *s, int c );
Diese Funktion liefert einen Zeiger auf das erste Auftreten von c in s oder NULL, falls c nicht in s vorkommt.
char *strrchr( const char *s, int c );
Diese Funktion liefert einen Zeiger auf das letzte Auftreten von c in s. oder NULL, falls c nicht in s vorkommt.
size_t strspn( const char *s, const char *set );
Diese Funktion liefert die Anzahl der Zeichen am Anfang von s, die sämtlich in set vorkommen.
size_t strcspn( const char *s, const char *set );
Diese Funktion liefert die Anzahl der Zeichen am Anfang von s, die sämtlich nicht in set vorkommen.
char *strpbrk( const char *s, const char *set );
Diese Funktion liefert einen Zeiger auf das erste Zeichen, das in set vorkommt oder NULL, falls keines vorkommt.
char *strstr( const char *src, const char *sub );
Diese Funktion liefert einen Zeiger auf das erste Vorkommen von sub in src oder NULL, falls sub nicht in set vorkommt.
char *strtok( char *str, const char *set );
Diese Funktion durchsucht s nach Zeichenfolgen, die durch ein Zeichen aus set begrenzt sind. Der erste Aufruf wird mit dem zu untersuchenden String str gemacht und liefert die erste Zeichenfolge. Anschließend wird strtok solange mit NULL für str aufgerufen, wie sie einen Wert ungleich Null und damit das nächste Token liefert. Die Auswahl an Trennern in set kann bei jedem Aufruf verschieden sein.
char *strerror( int errnum );
Diese Funktion liefert einen Zeiger auf den Fehlertext, der für den Fehler errnum definiert ist. Bei der Fehlernummer kann es sich um den Wert von errno handeln.
void *memchr( const void *ptr, int val, size_t len );
Diese Funktion liefert einen Zeiger auf das erste Auftreten von val im Speicherblock ptr oder NULL, falls val nicht in den ersten len Zeichen von ptr vorkommt.
int memcmp( const void *ptr1, const void *ptr2, size_t len );
Diese Funktion vergleicht die ersten len Zeichen von ptr2 mit ptr1. Das Ergebnis ist das Gleiche wie bei strncmp.
void *memcpy( void *dest, const void *src, size_t len );
Diese Funktion kopiert len Zeichen von src nach dest.
void *memmove( void *dest, const void *src, size_t len );
Diese Funktion kopiert wie memcpy, funktioniert aber auch, wenn sich src und dst überlappen.
void *memset( void *ptr, int val, size_t len );
Diese Funktion setzt len Zeichen von ptr auf den Wert val.

Stringfunktionen in cookie.c

Mit den Stringfunktionen lassen sich die 4 Bytes des Cookie-Ids in einen String kopieren und damit auch als String ausgeben, wenn sie aus darstellbaren Zeichen ohne Leerzeichen bestehen. Für einen Benutzer ist es einfacher, mit dem Kürzel zu arbeiten, da es aussagekräftiger ist und sich damit auch leichter merken lässt. Die Kopie wurde mit memcpy durchgeführt, da die abschließende Null im Cookie nicht vorhanden ist und deshalb immer 4 Bytes kopiert werden müssen.

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "cookie.h"

void CookieSetL(CookieEntry *Cookie,long Name,long Value)
{
   Cookie->name.name_long = Name;
   Cookie->value = Value;
}

void CookieSetS(CookieEntry *Cookie,char *Name,long Value)
{
   memcpy(Cookie->name.name_array,Name,4);
   Cookie->value = Value;
}

void CookiePrint(CookieEntry *Cookie)
{
   char Name[5];

   if (isgraph(Cookie->name.name_array[0]) &&
       isgraph(Cookie->name.name_array[1]) &&
       isgraph(Cookie->name.name_array[2]) &&
       isgraph(Cookie->name.name_array[3]))
   {
      memcpy(Name,Cookie->name.name_array,4);
      Name[4] = '\0';
      printf("Name des Cookies: %s\n", Name);
   }
   else
      printf("Name des Cookies: %d\n", Cookie->name.name_long);
   printf("Wert des Cookies: %d\n", Cookie->value);
}

void CookieInput(CookieEntry *Cookie)
{
   printf("Name des Cookies (long): ");
   scanf("%ld\n", &(Cookie->name.name_long));
   printf("Wert des Cookies (long): ");
   scanf("%ld\n", &(Cookie->value));
}

int CookieIsNullCookie(CookieEntry *Cookie)
{
   return Cookie->name.name_long == NULL_COOKIE;
}

int CookieIsCookie(CookieEntry *Cookie,long Name)
{
   return Cookie->name.name_long == Name;
}

int CookieRead(CookieEntry *Cookie,FILE *stream)
{
   return(fread(Cookie,sizeof(CookieEntry),1,stream) == 1);
}

int CookieWrite(CookieEntry *Cookie,FILE *stream)
{
   return(fwrite(Cookie,sizeof(CookieEntry),1,stream) == 1);
}

Stringfunktionen in cjar.c

Auch für die Suche nach einem bestimmten Cookie werden die Stringfunktionen benötigt. Da sich ein Vergleich eines long schneller durchführen lässt als ein strncmp, wurde in cookie.c nur eine Vergleichsfunktion für den Namen als long implementiert. Deshalb müssen die 4 Bytes des Cookie-Ids von der Zeichenkette in einen long kopiert werden. Dazu wird eine long-Variable deklariert und mittels memcpy werden die 4 Bytes des Cookienamens an den Speicherplatz kopiert, den der long belegt. Da sich solche Tricks auf eine bestimmte Größe der Datentypen verlassen, sind sie nicht unbedingt portabel. Außerdem erschweren sie das Verständnis des Programms. In diesem Fall ist der Trick aber gerechtfertigt, weil der Name des Cookies zwar in der Regel ein Kürzel ist, aber keinen nullterminierten C-String darstellt.

#ifdef __TURBOC__
#include <tos.h>
#else
#ifdef __GNUC__
#include <osbind.h>
#else
#include <tosbind.h>
#endif
#endif

#include <stddef.h>
#include <string.h>
#include cookie.h
#include cjar.h

#define _p_cookies (void *)0x5a0l

CookieEntry *CookieJar = NULL;

int CjarInit(void)
{  long OldStack;

   OldStack = Super(0L);
   CookieJar = *((CookieEntry**)_p_cookies);
   Super((void *)OldStack);
   return (CookieJar != NULL);
}

CookieEntry *CjarSearchL(long Name)
{  CookieEntry *AktCookie;

   if (CookieJar != NULL)
      CjarInit();
   if (CookieJar != NULL)
   {
      AktCookie = CookieJar;
      while (!CookieIsNullCookie(AktCookie) &&
             !CookieIsCookie(AktCookie,Name))
      {
         AktCookie++;
      }
      if (CookieIsNullCookie(AktCookie))
         return NULL;
      else
         return AktCookie;
   }
   else
      return NULL;
}

CookieEntry *CjarSearchS(char *Name)
{  long CookieNameAsLong;

   memcpy(&CookieNameAsLong, Name, 4);
   return CjarSearchL(CookieNameAsLong);
}

void CjarTraverse(CookieAction ActionFkt)
{  CookieEntry *AktCookie;

   if (CookieJar != NULL)
      CjarInit();
   if (CookieJar != NULL)
   {
      AktCookie = CookieJar;
      while (!CookieIsNullCookie(AktCookie))
      {
         (*ActionFkt)(AktCookie);
         AktCookie++;
      }
   }
}

Die nächste Folge zeigt Einblicke in die math.h. Dort sind häufig benötigte mathematische Funktionen zusammengefasst.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil XII

math.h

In math.h sind mathematische Funktionen definiert.

double fabs( double x );
Diese Funktion liefert den Absolutbetrag.
double ceil( double x );
Diese Funktion rundet auf den nächsten ganzzahligen Wert auf.
double floor( double x );
Diese Funktion rundet auf den nächsten ganzzahligen Wert ab.
double fmod( double x, double y );
Diese Funktion liefert den Rest, wenn x durch y geteilt wird.
double exp( double x );
Diese Funktion liefert e hoch x.
double log( double x );
Diese Funktion liefert den natürlichen Logarithmus von x.
double log10( double x );
Diese Funktion liefert den Logarithmus zur Basis 10 von x.
double frexp( double x, int *exp);
Diese Funktion zerlegt x in eine normalisierte Mantisse im Bereich von 1/2 .. 1, die als Ergebnis geliefert wird und eine Potenz von 2, die in exp geschrieben wird.
double ldexp( double x, int n );
Diese Funktion liefert x * (2 hoch n).
double pow( double x, double y );
Diese Funktion liefert x hoch y.
double sqrt( double x );
Diese Funktion liefert die Wurzel von x.
double cos( double x );
Diese Funktion liefert den Cosinus von x.
double sin( double x );
Diese Funktion liefert den Sinus von x.
double tan( double x );
Diese Funktion liefert Tangens von x.
double acos( double x );
Diese Funktion ist die Umkehrfunktion zu cos.
double asin( double x );
Diese Funktion ist die Umkehrfunktion zu sin.
double atan( double x );
Diese Funktion ist die Umkehrfunktion zu tan.
double atan2( double x, double y );
Diese Funktion liefert den arctan von y/x.
double cosh( double x );
Diese Funktion liefert den Cosinus Hyperbolicus von x.
double sinh( double x );
Diese Funktion liefert den Sinus Hyperbolicus von x.
double tanh( double x );
Diese Funktion liefert Tangens Hyperbolicus von x.
double acosh( double x );
Diese Funktion ist die Umkehrfunktion zu cosh.
double asinh( double x );
Diese Funktion ist die Umkehrfunktion zu sinh.
double atanh( double x );
Diese Funktion ist die Umkehrfunktion zu tanh.

stdlib.h

In stdlib.h sind diverse Funktionen definiert, die sich nicht in die anderen Bibliotheken einordnen lassen.

double atof( const char *str );
Diese Funktion wandelt einen String in einen double um.
int atoi( const char *str );
Diese Funktion wandelt einen String in einen int um.
long atol( const char *str );
Diese Funktion wandelt einen String in einen long um.
void *malloc( size_t size );
Diese Funktion liefert einen Zeiger auf einen Speicherblock der Größe size. Wenn der Speicher nicht mehr benötigt wird, muss er mit free wieder freigegeben werden.
void *calloc( size_t elt_count, size_t elt_size );
Diese Funktion liefert einen Speicherblock für elt_count Elemente der Größe elt_size. Der Speicher wird von calloc mit 0 initialisiert.
void free( void *ptr );
Diese Funktion gibt den Speicher, der mit malloc, calloc oder realloc angefordert wurde, wieder an die Speicherverwaltung zurück.
void *realloc( void *ptr, size_t size );
Diese Funktion ändert die Größe des Speicherbereichs ptr auf size. Der bisherige Inhalt von ptr bleibt erhalten.
int abs( int x );
Diese Funktion liefert den Absolutbetrag von x als int, schneidet also das Vorzeichen ab.
long labs( long x );
Diese Funktion liefert den Absolutbetrag von x als int.
div_t div( int n, int d );
Diese Funktion teilt n durch d und liefert das Ergebnis in der Struktur vom Typ div_t, die das ganzzahlige Ergebnis und den Rest enthält.
ldiv_t ldiv( long n, long d );
Diese Funktion arbeitet wie div, aber mit long- statt int-Werten.
int rand( void );
Diese Funktion liefert eine ganzzahlige Pseudo-Zufallszahl zwischen 0 und RAND_MAX.
void srand( unsigned int seed );
Diese Funktion setzt seed als neuen Startwert für eine neue Zufallszahlenfolge.
double strtod( const char *s, char **endptr );
Diese Funktion wandelt einen String in einen double um. Für endptr kann die Adresse eines Pointers übergeben werden. Dieser Pointer wird dann auf das erste Zeichen aus s gesetzt, das nicht mehr für die Konvertierung benutzt wird. Wird für endptr NULL übergeben, ist die Funktion äquivalent zu atod.
long strtol( const char *s, char **endptr, int base );
Diese Funktion arbeitet wie strtod, liefert aber einen long und in base wird die Basis des verwendeten Zahlensystems übergeben. Hat base den Wert 0, wird versucht, die Umwandlung für die Basis 8, 10 oder 16 durchzuführen. In diesem Fall zeigt eine führende Null das Oktalsystem und ein führendes 0x oder 0X das Hexadezimalsystem an.
unsigned long strtoul( const char *s, char **endptr, int base );
Diese Funktion arbeitet wie strtoul, liefert aber einen unsigned long.
int system( const char *command );
Diese Funktion übergibt den String command an das System zur Ausführung. Sowohl der Aufbau des Kommandos als auch das Ergebnis sind von dem verwendeten System und dem Kommandointerpreter abhängig und damit nicht portabel!
void exit( int status );
Diese Funktion beendet das Programm normal mit dem Returnwert status. Es werden auch die mit atexit gesetzten Funktionen aufgerufen. Um den Programmlauf übersichtlich zu halten, sollte ein Programm allerdings möglichst mit dem Ende von main terminieren.
void abort( void );
Diese Funktion sorgt für eine anormale Beendigung des Programms.
int atexit( void (*func)( void ) );
Diese Funktion setzt eine Funktion, die bei Beendigung des Programms aufgerufen wird. Konnte die Funktion nicht gesetzt werden, wird ein Wert ungleich Null zurück geliefert. Wenn ein Programm immer mit dem Ende von main terminiert, kann auch dort eine solche Aufräumfunktion aufgerufen werden.
char *getenv( const char *name );
Diese Funktion liefert den Wert der Umgebungs- bzw. Environmentvariable name. Eine sehr bekannte Umgebungsvariable ist z.B. PATH.
void *bsearch( const void *key, const void *base, size_t nmemb, size_t size, int (*compar)() );
Diese Funktion sucht in dem Feld base nach dem Eintrag key. Der Parameter nmemb gibt an, wieviel Einträge das Feld enthält und size die Größe von jedem Element. Das Feld muss aufsteigend sortiert sein und der Vergleich wird mit der Funktion compar durchgeführt. Diese Funktion bekommt zwei Zeiger auf Elemente und muss einen Wert kleiner 0 liefern, wenn der erste Parameter kleiner ist, 0 für Gleichheit und größer 0, wenn der zweite Parameter kleiner ist.
void qsort( void *base, size_t nmemb, size_t size, int (*compar)() );
Diese Funktion sortiert das Feld base in aufsteigender Reihenfolge. Das Feld enthält nmemb Elemente der Größe size. Der Vergleich wird von der Funktion compar durchgeführt, für die das Gleiche gilt wie unter bsearch beschrieben.

Der nächste Abschnitt befasst sich mit der Include-Datei assert.h, die Hilfen für die Fehlersuche bereitstellt.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende 

ANSI C - Ein Programmierkurs - Teil XIII

assert.h

Die Includedatei assert.h definiert das Makro assert, mit dem sich Testpunkte in einem Programm einfügen lassen. Als Parameter bekommt das Makro einen Ausdruck. Hat der Ausdruck den Wert 0, dann wird das Programm angehalten und eine Fehlermeldung ausgegeben, die folgendem Formatstring entspricht:

 "\nAssertion failed: %s, file %s, line %d\n"

Der erste Platzhalter gibt den Ausdruck wieder, den assert prüfen soll. Der Dateiname und die Zeilennummer werden durch den die Makros ./c-kurs.wml und 4757 des Preprozessors gesetzt.

Ist der Makroname NDEBUG (no debugging) definiert, wird das assert-Makro ignoriert. Damit können durch assert Überprüfungen während der Entwicklung gemacht werden. Bei der Programm-Version für die Auslieferung wird durch Definition von NDEBUG die Überprüfung ausgeschaltet, ohne dass das Makro assert aus dem Quelltext entfernt werden muss.

Beim Folgenden Beispiel wird mittels assert überprüft, ob der stream auch wirklich ungleich NULL ist. Ist er NULL, dann ist er mit Sicherheit nicht gültig, unser Ausdruck liefert einen Wert von 0 (Falsch) und das Programm wird angehalten. Schreiben Sie ein kleines Programm, das diese Funktion enthält und übergeben Sie einmal einen gültigen stream und einmal NULL. Dann versuchen Sie beides nochmals, nachdem Sie vor der Includeanweisung für assert.h die Zeile

#define NDEBUG

eingefügt haben. Für TC/PC gibt es in den Optionen für den Compiler das Editfeld define Macro, das auch dazu benutzt werden kann, NDEBUG für die Releaseversion zu definieren.

int WriteInFile(FILE *stream)
{
   assert(stream!=NULL);
   fputs("Hallo Welt",stream);
}

stdarg.h

In der Datei stdarg.h sind Funktionen für die Behandlung von variablen Parameterlisten definiert, wie sie z.B. die printf-Funktion benutzt.

void va_start(va_list ap, parmN)
Mit dem Makro va_start initialisiert man die Bearbeitung. Dazu übergibt man eine Variable vom Typ va_list, die dazu dient, die Parameter der Reihe nach abzuarbeiten, und den letzten Parameter, der einen Namen trägt.
#define va_arg(ap, type) (*((type *)(ap))++)
Das Makro va_arg bekommt die Variable vom Typ va_list und den Datentyp des nächsten Parameters und liefert den Parameter. Durch eine Folge von va_arg-Aufrufen kann man der Reihe nach jeden Parameter abarbeiten.
#define va_end(ap)
Sind alle Parameter abgearbeitet worden, muss vor Verlassen der Funktion dieses Makro aufgerufen werden.

Um mit variablen Parameterlisten sinnvoll umzugehen, muss die Funktion erkennen können, welche Parameter folgen. Bei den formatierten Ausgaben (printf...) erfolgt dies durch die Platzhalter im Formatstring.


void Test(int x,...)
{
   va_list ap;
   int i;

   puts("va_arg - Test");
   va_start(ap, x);
   for (i = 0; i < x; i++)
   {
      printf("Parameter %d hat den Wert %d\n", i, va_arg(ap,int) + 2);
   }
}
 
int main(void)
{
   Test(1,34);
   Test(3,34,42,65);
}

setjmp.h

Die Definitionen aus setjmp.h erlauben es, den normalen Ablauf eines Programms zu umgehen. Damit wird üblichereise im Fehlerfall direkt aus tief verschachtelten Funktionsaufrufen zurückgesprungen. Leider wird hierdurch der Programmablauf etwas durchbrochen. Bei einer guten Aufteilung eines Programms auf Module und Bibliotheken, die auch allgemeingültig verwendbar sind, sollten die Funktionen der Module sich geordnet beenden und einen entsprechenden Returnwert liefern.

void longjmp(jmp_buf jmp_buffer, int return_value);
Mit dieser Funktion stellt man den Zustand wieder her, der mit setjmp gespeichert wurde. Die Programmausführung geht dann so weiter, als wurde setjmp mit dem Returnwert return_value, der ungleich 0 sein muss, beendet. Die Funktion, aus der setjmp aufgerufen wurde, darf noch nicht beendet sein! Globale Variablen haben wieder den Wert von vor dem Aufruf von setjmp, der Wert von automatischen Variablen aus der Funktion, die setjmp aufruft, ist undefiniert.
int setjmp(jmp_buf jmp_buffer);
Diese Funktion speichert den aktuellen Zustand in jmp_buffer ab, um ihn später wieder anspringen zu können. Diese Funktion liefert in diesem Fall 0 zurück. Wenn mit longjmp zu dieser Stelle zurückgesprungen wird, befindet man sich hinter diesem Aufruf, aber mit einem Returnwert ungleich 0.

Damit das Verhalten etwas deutlicher wird, folgt ein kleines Beispiel:

#include <stdio.h>
#include <setjmp.h>

int i;

int main(void)
{
   jmp_buf TestEnv;

   i = 0;
   printf("i vor setjmp = %d\n", i);
   if (setjmp(TestEnv) == 0)
   {
      puts("direkter Aufruf");
	  i = 4;
      printf("i vor longjmp = %d\n", i);
	  longjmp(TestEnv, 1);
   }
   else
   {
      puts("hierher durch Aufruf von longjmp");
      printf("i nach setjmp = %d\n", i);
   }
   puts("Programmende");
}

signal.h

In der Datei signal.h sind Funktionen für Ausnahmebehandlung bzw. Exceptions definiert. Es kann sich dabei um Fehler in der Programmausführung als auch um Signale von einem anderen Prozeß handeln. Unter UNIX oder auch MiNT mit den entsprechenden Tools ist es möglich, mittels kill ein solches Signal an ein Programm zu senden.

typedef void (*sigfunc_t)( int );
Dies ist die Definition für einen Zeiger auf die Exceptionroutine.
sigfunc_t signal( int sig, sigfunc_t func );
Mit diese Funktion setzt man eine eigene Funktion für eine bestimmte Exception. Der Parameter sig gibt an, für welche Ausnahme die Funktion func zuständig sein soll. Entsprechende Konstanten sind in signal.h definiert. Als Funktion kann auch SIG_DFL oder SIG_IGN angegeben werden. SIG_DFL bedeutet, dass der Default-Handler des Systems benutzt wird. SIG_IGN bedeutet, dass das Signal ignoriert wird.
int raise( int sig );
Mit dieser Funktion sendet man ein Signal an sich selbst.

time.h

In time.h sind Funktionen für den Umgang mit Datum und Uhrzeit definiert. Einige Funktionen benutzen den Datentyp tm, der den weiter unten aufgeführten Aufbau hat. Die Komponente tm_isdst ist positiv, wenn Sommerzeit gilt, 0, wenn keine Sommerzeit gilt und negativ, wenn diese Information nicht zur Verfügung steht. Die restlichen Komponenten sollten mit dem Kommentar selbsterklärend sein.

Man muss unterscheiden, ob eine Funktion die Kalenderzeit (die z.B. keine Zeitzonen berücksichtigt) oder die Ortszeit benutzt. Die Kalenderzeit, die in einen Datentyp time_t geschrieben wird, ist üblicherweise die Anzahl Sekunden seit dem 1.1.1970, also dem ersten 1. Januar seit dem Bestehen von UNIX.

struct tm
{
    int tm_sec;   /* Sekunden nach der vollen Minute -- [0,  59] */
    int tm_min;   /* Minuten nach der vollen Stunde  -- [0,  59] */
    int tm_hour;  /* Stunden nach Mitternacht        -- [0,  23] */
    int tm_mday;  /* Tag im Monat                    -- [1,  31] */
    int tm_mon;   /* Monate seit Januar              -- [0,  11] */
    int tm_year;  /* Jahre seit 1900                             */
    int tm_wday;  /* Tage seit Sonntag               -- [0,   6] */
    int tm_yday;  /* Tage seit 1. Januar             -- [0, 365] */
    int tm_isdst; /* Kennzeichen für Sommerzeit                  */
};
char *asctime( const struct tm *tblock );
Diese Funktion erzeugt aus der Zeit in tblock eine Zeichenkette, in der die Zeit in der Form "Sun Jan 3 15:14:13 1988\n" abgelegt ist. Wenn dieses Format nicht erwünscht ist, kann strftime benutzt werden.
char *ctime( const time_t *timer );
Diese Funktion gibt die Kalenderzeit in timer als Ortszeit in einem String aus. Damit ist die Funktion äquivalent zu asctime(localtime(timer));
struct tm *gmtime( const time_t *clock );
Diese Funktion wandelt die Kalenderzeit in tp in die Coordinated Universal Time (UTC).
struct tm *localtime( const time_t *clock );
Diese Funktion wandelt die Kalenderzeit in clock in die Ortszeit.
time_t time( time_t *timer );
Diese Funktion liefert die aktuelle Kalenderzeit. Wenn timer ungleich NULL ist, wird der Wert auch in die Variable geschrieben, auf die timer zeigt.
time_t mktime( struct tm *timeptr );
Diese Funktion wandelt die Ortszeit in timeptr in die Kalenderzeit um.
clock_t clock( void );
Diese Funktion liefert die Systemticks, die das Programm seit dem Start verbraucht hat. Die Konstante CLOCKS_PER_SEC liefert die Zahl Systemticks pro Sekunde.
size_t strftime( char *s, size_t max_size, const char *format, const struct tm *timeptr );
Diese Funktion wandelt die Zeit aus timeptr gemäß dem Formatstring format in einen String um, der nach s kopiert wird. Der Parameter max_size gibt dabei an, wieviel Zeichen s maximal fassen kann. Analog zu printf werden gewöhnliche Zeichen nach s übernommen und Platzhalter für strftime, die aus dem Prozentzeichen, gefolgt von einem Buchstaben, bestehen, werden durch den entsprechenden Wert aus der Struktur timeptr ersetzt.
double difftime( time_t time2, time_t time1 );
Diese Funktion liefert die Differenz von time2-time1 in Sekunden.

Platzhalter für strftime

  • [%a] abgekürzter Wochentag
  • [%A] ausgeschriebener Wochentag
  • [%b] abgekürzter Monatsname
  • [%B] voller Monatsname
  • [%c] Datum und Uhrzeit
  • [%d] Tag im Monat (1-31)
  • [%H] Stunde (0-23)
  • [%I] Stunde (0-12)
  • [%j] Tag im Jahr (1-366)
  • [%m] Monat (1-12)
  • [%M] Minute (00-59)
  • [%p] AM/PM
  • [%S] Sekunde (00-59)
  • [%w] Wochentag (0-6)
  • [%W] Woche im Jahr (0-52)
  • [%x] lokale Datumsdarstellung
  • [%X] lokale Zeit-Darstellung
  • [%y] Jahr ohne Jahrhundert (0-99)
  • [%Y] Jahr mit Jahrhundertangabe
  • [%Z] Name der Zeitzone (z.B. MEZ)
  • [%%] das '%'-Zeichen

limits.h

In der Datei limits.h sind die Grenzen von Datentypen definiert, also den kleinsten und den größten Wert, den ein Datentyp in dieser Implementierung aufnehmen kann.

#define  CHAR_BIT            8
#define  SCHAR_MIN        -128
#define  SCHAR_MAX         127
#define  UCHAR_MAX         255U
#define  CHAR_MIN         -128
#define  CHAR_MAX          127
#define  MB_LEN_MAX          1
#define  SHRT_MIN       -32768
#define  SHRT_MAX        32767
#define  USHRT_MAX       65535U
#define  INT_MIN        -32768
#define  INT_MAX         32767
#define  UINT_MAX        65535U
#define  LONG_MIN  -2147483648L
#define  LONG_MAX   2147483647L
#define  ULONG_MAX  4294967295LU

float.h

Die Datei floats.hbeschreibt wie limits.h die Grenzen von Datentypen, allerdings für float- und double-Werte.

#define  FLT_ROUNDS                      1
#define  FLT_RADIX                       2
#define  FLT_MANT_DIG                   24
#define  FLT_DIG                         6
#define  FLT_MIN_EXP                  -125
#define  FLT_MIN_10_EXP                -37
#define  FLT_MAX_EXP                   128
#define  FLT_MAX_10_EXP                 38
#define  FLT_EPSILON          1.192093E-07
#define  FLT_MIN              1.175494E-38
#define  FLT_MAX              3.402823E+38
#define  DBL_MANT_DIG                         64
#define  DBL_DIG                              19
#define  DBL_MIN_EXP                      -16383
#define  DBL_MIN_10_EXP                    -4932
#define  DBL_MAX_EXP                       16384
#define  DBL_MAX_10_EXP                     4932
#define  DBL_EPSILON  5.421010862427522170E-0020
#define  DBL_MIN      1.681051571556046753E-4932
#define  DBL_MAX      1.189731495357231765E+4932
#define  LDBL_MANT_DIG                        64
#define  LDBL_DIG                             19
#define  LDBL_MIN_EXP                     -16383
#define  LDBL_MIN_10_EXP                   -4932
#define  LDBL_MAX_EXP                      16384
#define  LDBL_MAX_10_EXP                    4932
#define  LDBL_EPSILON 5.421010862427522170E-0020
#define  LDBL_MIN     1.681051571556046753E-4932
#define  LDBL_MAX     1.189731495357231765E+4932

Der folgende, letzte Abschnitt liefert eine kurze Übersicht und Erklärung von Compiler-Optionen der verschiedenen Systeme.

Michael Bernstein


Anfang zurück vorwärts Ende  Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster

ATOS-Magazin: Homepage - C-Kurs
Das ATOS-Magazin
 
  zurück zum News-Archiv
Anfang zurück vorwärts Ende

ANSI C - Ein Programmierkurs - Teil XIV

Compilerschalter/-optionen

Hier folgt eine kurze Übersicht und Erklärung von Compileroptionen. Da Sozobon und erst recht der GCC eine größere Anzahl von Schaltern kennen, kann hier nur eine Auswahl vorgestellt werden. Für eine weitergehende Beschreibung wird auf die jeweilige Dokumentation verwiesen.

TC-Compileroptionen

Die Optionen für TC/PC findet man unter Options im Menüpunkt Compiler.

-A (ANSI keywords only)
Diese Option verbietet die TC-eigenen Erweiterungen des ANSI-Standards. Für portable Programme sollte deshalb diese Option gesetzt werden.
-C (Allow nested comments)
Diese Option erlaubt verschachtelte Kommentare. Für portable Programme sollte deshalb diese Option nicht gesetzt werden.
-Y (Add debug Information)
Diese Option erzeugt Informationen für den Turbo-Debugger.
-T (Stack checking)
Diese Option erzeugt Code, der den Stack zur Laufzeit überprüft. Dadurch wird das Programm zwar langsamer, aber es können noch Fehler oder auch nur eine ungünstige Einstellung für die Stackgröße gefunden werden.
-B (Generate standard Object)
Mit dieser Option erzeugt der Compiler das DRI-Object-Format. Allerdings werden in diesem Objektformat nur 8 Zeichen von Bezeichner unterschieden und es sind keine Debug-Information möglich. Diese Option ist nur dann wichtig, wenn das Objektfile zu einem Programm in einer anderen Sprache hinzugelinkt wird.
-X (Generate underbars)
Diese Option setzt vor jedes Symbol einen '_'. Diese Option ist nur dann wichtig, wenn das Objektfile zu einem Programm in einer anderen Sprache hinzugelinkt wird.
-H (Use cdecl calling)
Diese Option weist den Compiler an, die Parameter für Funktionen auf dem Stack zu übergeben und das Ergebnis in Register D0 zurückzugeben.
-Q (Use pascal calling)
Diese Option weist den Compiler an, die Parameter von links nach rechts auf dem Stack zu übergeben und das Ergebnis in D0 zurückzugeben.
-K (Default char is unsigned)
Diese Option setzt den Standard-Datentyp für char auf unsigned. Achtung: wenn mittels dieser Option zwischen signed und unsigned char umgeschaltet wird, sind die Programme nicht mehr portabel!
-J (No jump optimization)
Diese Option weist den Compiler an, keine Programmsprünge zu optimieren.
-Z (No register optimization)
Diese Option weist den Compiler an, bereits geladene Register-Werte nicht zu berücksichtigen
-M (No string merging)
Diese Option weist den Compiler an, doppelte Strings nicht zusammenlegen. Dies ist dann wichtig, wenn Stringkonstanten verändert werden, weil sie z.B. auch als Platzhalter dienen
-R (No register variables)
Diese Option weist den Compiler an, keine Register-Variablen zu verwenden.
-G (Size optimization)
Diese Option weist den Compiler an, die Programmgröße zu optimieren.
-P (Use absolute calls)
Diese Option weist den Compiler an, absolute Unterprogrammaufrufe zu verwenden. Da ein relativer Sprung beim 68000 nur über +-32KB gehen kann, ist dies für größere Programme erforderlich. Andernfalls liefert der Linker eine entsprechende Fehlermeldung.
-S (Standard stack frames)
Diese Option weist den Compiler an, in Funktionen die Link- und die Unlink-Anweisungen des Prozessors zu verwenden, um Platz auf dem Stack für lokale Variablen anzulegen.
-2 (Generate 68020 Code)
Diese Option weist den Compiler an, Code für einen 68020-Prozessor oder größer zu erzeugen. Es sollte aber normalerweise keinen Grund geben, die älteren Ataris (1040, Mega ST, ...) auszusperren, da diese Optimierung nur einen geringen Vorteil bietet.
-8 (Generate 68881 Code)
Diese Option weist den Compiler an, Code für den 68881-Coprozessor zu erzeugen. Damit läuft das Programm nur noch auf Ataris mit FPU. Wenn ein Programm intensiv Fließkommaberechnung benutzen muss, ist diese Optimierung sinnvoll.

Maximum identifier length ist die maximale Länge für Bezeichner, die noch unterschieden werden können.

Warning level gibt die Warnstufe von 0 bis 2 an. Je höher die Warnstufe ist, desto mehr Dinge werden vom Compiler bemängelt. Da einige Warnings auch zu Fehlern führen könnten, sollte die Warnstufe immer so hoch wie möglich eingestellt werden und so programmiert werden, dass keine Warnungen erscheinen. Es gibt z.B. ein Warning, wenn ein Returnwert einer Funktion nicht gesetzt wird. Wird dieser Returnwert aber abgefragt, kann dies zu einem unerwünschten Verhalten des Programms führen.

Sozobon-Compilerschalter

Bei den Schaltern für Sozobon handelt es sich um Kommandozeilenparameter.

-c
Dieser Schalter weist den Compiler an, die Datei nur zu compilieren und den Linker nicht aufzurufen. Das daraus resultierende Objektfile hat den gleichen Namen wie die Quelldatei, aber mit .o als Extension. Dieser Schalter wird benötigt, wenn eine Programm aus mehr als einer Datei besteht. Dann muss jede Datei nur übersetzt und sämtliche Objektdateien zum Programm zusammengelinkt werden.
-S
Sozobon übersetzt eine Quelldatei zu einer Objektdatei in zwei Schritten. Im ersten Schritt wird eine Assemblerdatei erzeugt, die anschließend von einem Assembler übersetzt wird. Dieser Schalter weist den Compiler an, nur die Assemblerdatei zu erzeugen. Damit ist der Schalter gerade für einen Anfänger weniger wichtig. Wer aber etwas Assembler beherrscht, kann einmal seine Neugierde befriedigen.
-P
Dieser Schalter weist den Compiler an, nur den Preprozessor zu starten. Dies kann manchmal recht nützlich sein, wenn ein Makro nicht das tut, was es soll. Die Ausgabedatei hat .i als Extension.
-o <file>
Dieser Schalter setzt den Namen des Outputfiles auf <file>. Dies ist wichtig für den Linkvorgang, da der Linker sonst den Namen a.out benutzt.
-f
Mit diesem Schalter weist man den Linker an, Bibliotheken mit Fließkommaroutinen zu linken. Ohne diesen Schalter versucht der Linker, die Standardbibliothek ohne Fließkommaunterstützung zu verwenden.
-F <file>
Mit diesem Schalter weist man den Linker an, aus der Datei file die Objektdateien und die Bibliotheken zu ermitteln, die er zusammenlinken soll. Der Aufbau dieser Datei ist in der Dokumentation beschrieben. Wenn ein Projekt aus mehreren Dateien besteht, ist diese Option ist eine Möglichkeit, alle Dateien dem Linker anzugeben.
-l <name>
Mit diesem Schalter weist man den Linker an, eine Bibliothek mit dem Dateinamen 'lib<name>.a' oder '<name>.a' zu dem Programm zu linken.
-L<path>
Mit diesem Schalter gibt man dem Linker einen weiteren Pfad bekannt, den er nach den Bibliotheken durchsucht, die er zulinken möchte.
-I<path>
Mit diesem Schalter gibt man dem Preprozessor einen weiteren Pfad bekannt, den er für die include-Anweisung nach Headerdateien durchsucht.
-D<sym>
Mit diesem Schalter definiert man für den Preprozessor das Symbol sym mit dem Wert 1. Dies ist z.B. nützlich, um für die auszuliefernde Version des Programms NDEBUG zu definieren und damit das Makro assert auszublenden.
-D<sym=val>
Dieser Schalter ist gleichbedeutend mit der Preprozessoranweisung
#define sym val
-W<n>
Dieser Schalter setzt die Anzahl Fehler oder Warnungen, nach der der Compiler seine Arbeit abbricht.

GCC-Compilerschalter

Auch bei den Schaltern für GCC handelt es sich um Kommandozeilenparameter. Das Verhalten von GCC lässt sich hier im Vergleich zu TC/PC oder auch Sozobon sehr fein steuern, so dass hier nur eine kleine Auswahl vorgestellt werden kann.

-c
Dieser Schalter weist den Compiler an, die Datei nur zu compilieren und den Linker nicht aufzurufen. Das daraus resultierende Objektfile hat den gleichen Namen wie die Quelldatei, aber mit .o als Extension. Dieser Schalter wird benötigt, wenn ein Programm aus mehr als einer Datei besteht. Dann muss jede Datei nur übersetzt und sämtliche Objektdateien zum Programm zusammengelinkt werden.
-S
GCC übersetzt eine Quelldatei zu einer Objektdatei in zwei Schritten. Im ersten Schritt wird eine Assemblerdatei erzeugt, die anschließend von einem Assembler übersetzt wird. Dieser Schalter weist den Compiler an, nur die Assemblerdatei zu erzeugen. Damit ist der Schalter gerade für einen Anfänger weniger wichtig. Wer aber etwas Assembler beherrscht, kann einmal seine Neugierde befriedigen.
-E
Dieser Schalter weist den Compiler an, nur den Preprozessor zu starten. Dies kann manchmal recht nützlich sein, wenn ein Makro nicht das tut, was es soll. Die Ausgabe wird auf die Standardausgabe, also auf den Monitor ausgegeben und kann mittels Ausgabeumlenkung in eine Datei geschrieben werden.
-o <file>
Dieser Schalter setzt den Namen des Outputfiles auf <file>. Dies ist wichtig für den Linkvorgang, da der Linker sonst den Namen a.out benutzt.
-funsigned-char
Diese Option setzt den Standard-Datentyp für char auf unsigned. Achtung: wenn mittels dieser Option zwischen signed und unsigned char umgeschaltet wird, sind die Programme nicht mehr portabel!
-D<sym>
Mit diesem Schalter definiert man für den Preprozessor das Symbol sym mit dem Wert 1. Dies ist z.B. nützlich, um für die auszuliefernde Version des Programms NDEBUG zu definieren und damit das Makro assert auszublenden.
-D<macro=defn>
Dieser Schalter ist gleichbedeutend mit der Preprozessoranweisung
#define sym val
-l<library>
Mit diesem Schalter weist man den Linker an, eine Bibliothek mit dem Dateinamen 'lib<name>.a' zu dem Programm zu linken.
-I<dir>
Mit diesem Schalter gibt man dem Preprozessor einen weiteren Pfad bekannt, den er für die include-Anweisung nach Headerdateien durchsucht.
-L<dir>
Mit diesem Schalter gibt man dem Linker einen weiteren Pfad bekannt, den er nach den Bibliotheken durchsucht, die er zulinken möchte.
-pedantic
Mit diesem Schalter weist man den Compiler an, sich bei der Überprüfung strikt an den ANSI-Standard zu halten.
-pedantic-errors
Dieser Schalter arbeitet wie -pedantic, führt aber immer zu einem Error, wenn ansonsten nur eine Warnung erscheinen würde.
-W...
Diverse Varianten von -W schalten zusätzliche Überprüfungen ein und erzeugen eine Warnung, wenn der Test nicht zutrifft. Hier sollte man nicht sparsam sein, da einige der Warnungen auch auf einen Fehler im Programm hinweisen können. Welche Varianten es gibt, kann der Anleitung zu GCC bzw. der manual page entnommen werden. Die einfachste Möglichkeit ist es, -Wall für sämtliche Warnungen zu setzen.
-mc68020
Dieser Schalter weist den Compiler an, Code für einen 68020-Prozessor oder größer zu erzeugen. Dies ist die Grundeinstellung, wenn der GCC aus den Originalsourcen erstellt wird. Es sollte aber normalerweise keinen Grund geben, die älteren Ataris (1040, Mega ST, ...) auszusperren, da diese Optimierung nur einen geringen Vorteil bietet.
-mc68000
Dieser Schalter weist den Compiler an, Code zu generieren, der auch auf Computer mit 68000-CPU läuft.
-m68881
Dieser Schalter weist den Compiler an, Code für den 68881-Coprozessor zu erzeugen. Damit läuft das Programm nur noch auf Ataris mit FPU. Wenn ein Programm intensiv Fließkommaberechnung benutzen muss, ist diese Optimierung sinnvoll.
-mshort
Dieser Schalter weist den Compiler an, einen int als 16-bit-Wert zu betrachten.

Michael Bernstein


Anfang zurück vorwärts Ende Seitenanfang

Copyright und alle Rechte beim ATOS-Magazin. Nachdruck und Veröffentlichung von Inhalten nur mit schriftlicher Zustimmung der Redaktion.
Impressum - Rückmeldung via Mail oder Formular - Nachricht an webmaster