Programmiertipps

19 min read
Article updated on:25 Aug 2023

Source: https://www.cs.uky.edu/~raphael/programming.html

Von den Schülern der Junior- und Senior-Level-CS-Klassen wird erwartet, dass sie einige Hintergrundinformationen über C , Unix und Softwareentwicklungstools kennen . Auf dieser Seite werden einige dieser Hintergründe erläutert und einige Übungen vorgeschlagen .

C

  1. Mit C können Sie Variablen außerhalb jeder Prozedur deklarieren. Diese Variablen werden globale Variablen genannt .
  • Eine globale Variable wird beim Programmstart einmal zugewiesen und verbleibt im Speicher, bis das Programm beendet wird.
  • Eine globale Variable ist für alle Prozeduren in derselben Datei sichtbar.
  • Sie können eine in der Datei Ac deklarierte globale Variable für alle Prozeduren in einigen anderen Dateien Bc, Cc, Dc, ... sichtbar machen , indem Sie sie mit dem Modifikator extern in den Dateien Bc, Cc, Dc, ... deklarieren , wie in diesem Beispiel:
    extern int theVariable
    Wenn Sie viele Dateien haben, die die Variable gemeinsam nutzen, sollten Sie sie in einer Header-Datei foo.h ( unten beschrieben ) extern deklarieren und #include foo.h in den Dateien Bc, Cc, Dc, ... verwenden . Sie müssen die Variable in genau einer Datei ohne den externen Modifikator deklarieren, sonst wird sie überhaupt nicht zugewiesen.
  • Es ist keine gute Idee, zu viele globale Variablen zu verwenden, da Sie die Orte, an denen auf sie zugegriffen wird, nicht lokalisieren können. Es gibt jedoch Situationen, in denen Sie mit globalen Variablen die Übergabe vieler Parameter an Funktionen vermeiden können.
  1. Strings sind Zeiger auf nullterminierte Zeichenarrays.
  • Sie deklarieren eine Zeichenfolge als char *variableName .
  • Wenn Ihre Zeichenfolge konstant ist, können Sie ihr einfach ein Zeichenfolgenliteral zuweisen:
    char *myString = "Dies ist eine Beispielzeichenfolge";
  • Eine leere Zeichenfolge hat nur den Nullterminator:
    myString = ""; // leere Zeichenfolge, Länge 0, enthält Null

Ein Nullzeiger ist kein gültiger Stringwert:
myString = NULL; // ungültige Zeichenfolge
Sie können einen solchen Wert verwenden, um das Ende eines String-Arrays anzuzeigen:
argv[0] = "progName";

argv[1] = "firstParam";

argv[2] = "secondParam";

  • argv[3] = NULL; // Terminator

Wenn Ihre Zeichenfolge zur Laufzeit berechnet wird, müssen Sie genügend Speicherplatz reservieren, um sie aufzunehmen. Der Raum muss groß genug sein, um am Ende die Null aufzunehmen:
char *myString;

myString = (char *) malloc(strlen(someString) + 1); // Speicherplatz zuweisen

  • strcpy(myString, someString); // kopiere someString nach myString
  • Um Speicherlecks zu vermeiden, sollten Sie eventuell den mit malloc zugewiesenen Speicherplatz mithilfe von free zurückgeben . Sein Parameter muss der Anfang des von malloc zurückgegebenen Leerzeichens sein :
    free((void *) myString);
    Um Ihren Code sauber und lesbar zu halten, sollten Sie free() in derselben Prozedur aufrufen, in der Sie malloc() aufrufen . Sie können zwischen diesen beiden Punkten andere Prozeduren aufrufen, um Ihre Zeichenfolge zu bearbeiten.
  • Wenn Sie Zeichenfolgen kopieren, sollten Sie darauf achten, niemals mehr Bytes zu kopieren, als die Zieldatenstruktur aufnehmen kann. Pufferüberläufe sind die häufigste Ursache für Sicherheitslücken in Programmen. Erwägen Sie insbesondere die Verwendung von strncpy() und strncat() anstelle von strcpy() und strcat() .

Wenn Sie C++ verwenden, müssen Sie Ihre String- Objekte in Strings im C-Stil konvertieren, bevor Sie sie in einem Systemaufruf übergeben.
string myString // Diese Deklaration funktioniert nur in C++

...

  • someCall(myString.c_str())
    Leider gibt c_str() einen unveränderlichen String zurück. Wenn Sie eine veränderbare Zeichenfolge benötigen, können Sie die Daten entweder mit strcpy() kopieren (wie oben) oder den Typ umwandeln:
    someCall(const_cast<char *>(myString.c_str()))
    Das Umwandeln ist nicht so sicher wie das Kopieren, da someCall() den String tatsächlich ändern könnte, was jeden Teil des Programms verwirren würde, der davon ausgeht, dass myString konstant ist, was das übliche Verhalten von C++-Strings ist.
  1. Ein Puffer ist ein Speicherbereich, der als Container für Daten dient. Auch wenn die Daten möglicherweise eine Interpretation haben (z. B. ein Array von Strukturen mit vielen Feldern), werden sie von Programmen, die Puffer lesen und schreiben, häufig als Arrays von Bytes behandelt. Ein Byte-Array ist nicht dasselbe wie ein String, auch wenn beide als char * oder char [] deklariert sind .
  • Sie dürfen keine ASCII-Zeichen enthalten und dürfen nicht mit Null terminiert sein.
  • Sie können strlen() nicht verwenden , um die Länge der Daten in einem Puffer zu ermitteln (da der Puffer möglicherweise Nullbytes enthält). Stattdessen müssen Sie die Länge der Daten anhand des Rückgabewerts des Systemaufrufs (normalerweise read ) ermitteln, der die Daten generiert hat.
  • Sie können strcpy() , strcat() oder verwandte Routinen nicht für Bytepuffer verwenden. Stattdessen müssen Sie memcpy() oder bcopy() verwenden .

Mit Code wie dem folgenden schreiben Sie einen Puffer von 123 Byte in eine Datei:
char *fileName = "/tmp/foo"

#define BUFSIZE 4096

char buf[BUFSIZE]; // Puffer, der höchstens BUFSIZE Bytes enthält

...

int outFile; // Dateideskriptor, eine kleine Ganzzahl

int bytesToWrite; // Anzahl der Bytes, die noch geschrieben werden müssen

char *outPtr = buf;

...

if ((outFile = creat(fileName, 0660)) < 0) [ // Fehler

// SieheDateiberechtigungen, um 0660 zu verstehen

Fehler(Dateiname); // Ursache ausgeben

Ausgang(1); // und beenden

[

bytesToWrite = 123; // Initialisierung; 123 ist nur ein Beispiel

while ((bytesWritten = write(outFile, outPtr, bytesToWrite)) < bytesToWrite) [

// Es wurden noch nicht alle Bytes geschrieben

if (bytesWritten < 0) [ // Fehler

perror("write");

Ausgang(1);

[

outPtr += bytesWritten;

bytesToWrite -= bytesWritten;

  • [

Damit der Compiler Speicherplatz für Puffer zuweist, müssen Sie den Puffer mit einer Größe deklarieren, die der Compiler berechnen kann, wie in
#define BUFSIZE 1024

  • char buf[BUFSIZE];
    Wenn Sie den Puffer einfach ohne Größe deklarieren:
    char buf[];
    dann hat es eine unbekannte Größe und C weist keinen Speicherplatz zu. Das ist akzeptabel, wenn buf ein formaler Parameter ist (das heißt, er erscheint in einem Prozedur-Header); Der tatsächliche Parameter (vom Aufrufer bereitgestellt) hat eine Größe. Es ist jedoch nicht akzeptabel, wenn buf eine Variable ist. Wenn Sie die Größe des Puffers zur Kompilierungszeit nicht kennen, sollten Sie Code wie diesen verwenden:
    char *buf = (char *) malloc(bufferSize);
    Dabei ist bufferSize das Laufzeitergebnis einer Berechnung.
  1. Sie können Speicher dynamisch zuweisen und freigeben .

Einzelne Instanzen jeglicher Art:
typedef ... myType;

myType *myVariable = (myType *) malloc(sizeof(myType));

// Sie können jetzt auf *myVariable zugreifen.

...

  • free((void *) myVariable);
    Auch hier gilt es als gute Programmierpraxis , free() in derselben Routine aufzurufen , in der Sie malloc() aufrufen .

Eindimensionale Arrays jeglicher Art:
myType *myArray = (myType *) malloc(arrayLength * sizeof(myType));

// myArray[0] .. myArray[arrayLength - 1] werden jetzt zugewiesen.

...

  • free((void *) myArray);

Zweidimensionale Arrays werden durch ein Array von Zeigern dargestellt, die jeweils auf ein Array zeigen:
myType **myArray = (myType **) malloc(numRows * sizeof(myType *));

int rowIndex;

for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) [

myArray[rowIndex] = (myType *) malloc(numColumns * sizeof(myType));

[

// myArray[0][0] .. myArray[0][numColumns-1] .. myArray[numRows-1][numColumns-1]

// werden nun zugewiesen. Möglicherweise möchten Sie sie initialisieren.

...

for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) [

free((void *) myArray[rowIndex]);

[

free((void *) myArray);

  • Wenn Sie C++ verwenden, mischen Sie new/delete nicht mit malloc/free für dieselbe Datenstruktur. Der Vorteil von new/delete für Klasseninstanzen besteht darin, dass sie automatisch Konstruktoren aufrufen, die Daten initialisieren können, und Destruktoren, die Daten finalisieren können. Wenn Sie malloc/free verwenden , müssen Sie explizit initialisieren und finalisieren.
  1. Ganze Zahlen
  • C repräsentiert normalerweise ganze Zahlen in 4 Bytes. Beispielsweise wird die Zahl 254235 als Binärzahl 00000000,00000011,11100001,00011011 dargestellt.
  • Andererseits stellt ASCII-Text Zahlen wie jedes andere Zeichen dar, mit einem Byte pro Ziffer und einer Standardkodierung. In ASCII wird die Zahl 254235 als 00110010, 00110101, 00110110, 00110010, 00110011, 00110101 dargestellt.
  • Wenn Sie eine Datei mit Ganzzahlen schreiben müssen, ist es im Allgemeinen sowohl räumlich als auch zeitlich effizienter, die 4-Byte-Versionen zu schreiben, als sie in ASCII-Strings zu konvertieren und diese dann zu schreiben. So schreiben Sie eine einzelne Ganzzahl in eine geöffnete Datei:
    write(outFile, &myInteger, sizeof(myInteger))

Sie können die einzelnen Bytes einer Ganzzahl betrachten, indem Sie sie in eine Struktur aus vier Bytes umwandeln:
int IPAddress; // als Ganzzahl gespeichert, verstanden als 4 Bytes

typedef struct [

char byte1, byte2, byte3, byte4;

[ IPDetails_t;

IPDetails_t *details = (IPDetails_t *) (&IPAddress);

printf("Byte 1 ist %o, Byte 2 ist %o, Byte 3 ist %o, Byte 4 ist %o\n",

  • Details->Byte1, Details->Byte2, Details->Byte3, Details->Byte4);
  • Multibyte-Ganzzahlen können auf verschiedenen Computern unterschiedlich dargestellt werden. Einige (wie die Sun SparcStation) setzen das höchstwertige Byte an die erste Stelle; andere (wie der Intel i80x86 und seine Nachkommen) setzen das niedrigstwertige Byte an die erste Stelle. Wenn Sie ganzzahlige Daten schreiben, die möglicherweise auf anderen Computern gelesen werden, konvertieren Sie die Daten mit htons() oder htonl() in die Bytereihenfolge „Netzwerk“ . Wenn Sie ganzzahlige Daten lesen, die möglicherweise auf anderen Computern geschrieben wurden, konvertieren Sie die Daten mit ntohs() oder ntohl() von der „Netzwerk“-Reihenfolge in Ihre lokale Byte-Reihenfolge .

Sie können das Speicherlayout von Strukturen und den Wert vorhersagen, den sizeof() zurückgibt. Zum Beispiel,
struct foo [

char a; // verwendet 1 Byte

// C fügt hier ein 3-Byte-Pad ein, damit b an einer 4-Byte-Grenze beginnen kann

int b; // verwendet 4 Bytes

vorzeichenloses kurzes c; // verwendet 2 Bytes

unsigned char d[2]; // verwendet 2 Bytes

  1. [;
    Daher gibt sizeof(struct foo ) 12 zurück. Diese Vorhersagbarkeit (für eine gegebene Architektur) ist der Grund, warum manche C eine „portable Assemblersprache“ nennen. Sie müssen das Strukturlayout vorhersagen, wenn Sie Daten generieren, die einem bestimmten Format folgen müssen, beispielsweise einem Header in einem Netzwerkpaket.
  2. Sie können in C Zeiger auf jeden Typ deklarieren und ihnen Werte zuweisen, die auf Objekte dieses Typs zeigen.

Mit C können Sie insbesondere Zeiger auf ganze Zahlen erstellen:
int someInteger;

int *intPtr = &someInteger; // deklariert eine Zeigerwertvariable und weist einen entsprechenden Zeigerwert zu

someCall(intPtr); // übergibt einen Zeiger als tatsächlichen Parameter

  • someCall(&someInteger); // hat den gleichen Effekt wie oben
  • Eine AC-Bibliotheksprozedur, die einen Zeiger auf einen Wert annimmt, ändert diesen Wert höchstwahrscheinlich (er wird zu einem „out“- oder „in out“-Parameter). Im obigen Beispiel ist es sehr wahrscheinlich, dass someCall den Wert der Ganzzahl someInteger ändert .

Sie können einen Zeiger auf ein Array von Ganzzahlen erstellen und ihn verwenden, um dieses Array schrittweise zu durchlaufen.
#define ARRAY_LENGTH 100

int intArray[ARRAY_LENGTH];

int *intArrayPtr;

...

int Summe = 0;

for (intArrayPtr = intArray; intArrayPtr < intArray+ARRAY_LENGTH; intArrayPtr += 1) [

sum += *intArrayPtr;

  • [

Sie können einen Zeiger auf ein Array von Strukturen erstellen und ihn verwenden, um dieses Array schrittweise zu durchlaufen.
#define ARRAY_LENGTH 100

typedef struct [int foo, bar;[ pair_t; //pair_t ist ein neuer Typ

pair_t structArray[ARRAY_LENGTH]; // structArray ist ein Array von ARRAY_LENGTH-pair_t-Elementen

pair_t *structArrayPtr; // structArrayPtr zeigt auf ein pair_t-Element

...

int Summe = 0;

for (structArrayPtr = structArray; structArrayPtr < structArray+ARRAY_LENGTH; structArrayPtr += 1) [

sum += structArrayPtr->foo + structArrayPtr->bar;

  • [
  • Wenn Sie einem Zeiger eine Ganzzahl hinzufügen, wird der Zeiger um entsprechend viele Elemente vorgerückt, unabhängig von der Größe der Elemente. Der Compiler kennt die Größe und macht das Richtige.
  1. Ausgabe
  • Sie formatieren die Ausgabe mit printf oder seiner Variante fprintf .
  • Die Formatzeichenfolge verwendet %d , %s , %f , um anzugeben, dass eine Ganzzahl, eine Zeichenfolge oder eine Zahl in die Ausgabe eingefügt werden soll.
  • Die Formatzeichenfolge verwendet \t und \n zur Angabe von Tabulatoren und Zeilenumbrüchen.
  • Beispiel:
    printf("Ich denke, dass die Zahl %d %s\n ist", 13, "Glück");
  • Durch das Mischen von printf() , fprintf() und cout werden Elemente möglicherweise nicht in der erwarteten Reihenfolge gedruckt. Sie verwenden unabhängige Staging-Bereiche („Puffer“), die sie drucken, wenn sie voll sind.
  1. Die main()-Routine akzeptiert Funktionsparameter, die Befehlszeilenparameter darstellen .
  • Eine gängige Methode zum Schreiben der Hauptroutine ist folgende:
    int main(int argc; char *argv[]);
    Hier ist argc die Anzahl der Parameter und argv ein Array von Zeichenfolgen, also ein Array von Zeigern auf nullterminierte Zeichenarrays.

Konventionell ist das erste Element von argv der Name des Programms selbst.
int main(int argc; char *argv[]);

printf("Ich habe %d Parameter; mein Name ist %s und mein erster Parameter ist %s\n",

  • argc, argv[0], argv[1]);
  1. Praktische Sprachfunktionen
  • Mit dem Operator ++ können Sie eine Ganzzahl erhöhen oder einen Zeiger auf das nächste Objekt verweisen lassen . Normalerweise ist es am besten, diesen Operator nach der Variablen zu platzieren: myInt++ . Wenn Sie ++ vor die Variable setzen, wird die Variable erhöht, bevor sie ausgewertet wird, was selten das ist, was Sie wollen.

Sie können eine Zuweisung erstellen, bei der die Variable auf der linken Seite als erster Teil des Ausdrucks auf der rechten Seite beteiligt ist:
myInt -= 3; // entspricht myInt = myInt - 3

myInt *= 42; // entspricht myInt = myInt * 42

  • myInt += 1; // äquivalent zu myInt++ und möglicherweise besser als myInt++
  • Sie können Zahlen dezimal, oktal (durch Voranstellen der Ziffer 0 , wie in 0453 ) oder hexadezimal (durch Voranstellen der Ziffer 0x , wie in 0xffaa ) ausdrücken.

Sie können eine Ganzzahl als eine Menge von Bits behandeln und bitweise Operationen ausführen:
myInt = myInt | 0444; // bitweises ODER; 0444 ist oktal

myInt &= 0444; // bitweises UND mit einer Zuweisungskurzschrift

  • myInt = etwas ^ was auch immer; // bitweises XOR

C und C++ verfügen über bedingte Ausdrücke. Anstatt zu schreiben
wenn (a < 7)

a = irgendein Wert

anders

  • a = someOtherValue;
    Du kannst schreiben
    a = a < 7 ? someValue : someOtherValue;
  • Zuweisungen geben den Wert der linken Seite zurück, sodass Sie eine Zuweisung in größere Ausdrücke wie Bedingungen einschließen können. Sie sollten jedoch der Konvention folgen, dass solche Zuweisungen immer in Klammern gesetzt werden, um sowohl jemandem, der Ihren Code liest, als auch dem Compiler anzuzeigen, dass Sie wirklich eine Zuweisung und keinen Gleichheitstest meinen. Schreiben Sie zum Beispiel
    if ((s = socket(...)) == -1)
    nicht
    if (s = socket(...) == -1)
    Die zweite Version ist sowohl schwieriger zu lesen als auch in diesem Fall falsch, da der Gleichheitsoperator == eine höhere Priorität hat als der Zuweisungsoperator = .
  1. Programme, die nicht trivial kurz sind, sollten normalerweise in mehrere Quelldateien zerlegt werden , deren Name jeweils auf .c (für C-Programme) oder .cpp (für C++-Programme) endet.
  • Versuchen Sie, Funktionen, die dieselben Datenstrukturen bearbeiten oder verwandte Zwecke haben, in derselben Datei zu gruppieren.
  • Alle Typen, Funktionen, globalen Variablen und Manifestkonstanten, die von mehr als einer Quelldatei benötigt werden, sollten auch in einer Header-Datei deklariert werden , deren Name auf .h endet .
  • Deklarieren Sie außer bei Inline-Funktionen keine Funktionskörper (oder irgendetwas, das den Compiler veranlasst, Code zu generieren oder Speicherplatz zuzuweisen) in der Header-Datei.
  • Jede Quelldatei sollte mit einer #include- Zeile auf die benötigten Header-Dateien verweisen.
  • Fügen Sie niemals eine .c- Datei #ein .
  1. Wenn Sie über mehrere Quelldateien verfügen, müssen Sie alle kompilierten Objektdateien zusammen mit allen Bibliotheken, die Ihr Programm benötigt, miteinander verknüpfen .
  • Die einfachste Methode ist die Verwendung des C-Compilers, der sich mit den C-Bibliotheken auskennt:
    gcc *.o -o meinProgramm
    Dieser Befehl fordert den Compiler auf, alle Objektdateien mit der C-Bibliothek (die implizit enthalten ist) zu verknüpfen und das Ergebnis in der Datei myProgram abzulegen , die ausführbar wird.
  • Wenn Ihr Programm andere Bibliotheken benötigt, sollten Sie diese nach Ihren Objektdateien angeben, da der Linker nur Routinen aus Bibliotheken sammelt, von denen er bereits weiß, dass er sie benötigt, und die Dateien in der von Ihnen angegebenen Reihenfolge verknüpft. Wenn Sie also eine Bibliothek wie libxml2 benötigen , sollte Ihr Verknüpfungsbefehl etwa so lauten:
    gcc *.o -lxml2 -o meinProgramm
    Der Compiler weiß, wie er verschiedene Standardverzeichnisse nach der aktuellen Version von libxml2 durchsucht .
  1. Debuggen von C-Programmen
  • Wenn Sie einen Segmentierungsfehler erhalten, liegt höchstwahrscheinlich ein Index außerhalb des gültigen Bereichs, ein nicht initialisierter Zeiger oder ein Nullzeiger vor.
  • Sie können Druckanweisungen in Ihr Programm einfügen, um Ihnen bei der Lokalisierung eines Fehlers zu helfen.
  • Das Debuggen ist wahrscheinlich am erfolgreichsten, wenn Sie gdb ( unten beschrieben ) verwenden, um herauszufinden, wo Ihr Fehler liegt.
  • Programme, die über einen längeren Zeitraum ausgeführt werden, müssen darauf achten, den gesamten zugewiesenen Speicher freizugeben, da ihnen sonst irgendwann der Speicher ausgeht. Um Speicherlecks zu debuggen, können Sie diese Artikel zum Debuggen von C-Speicherlecks und C++-Speicherlecks lesen .

Unix

Standarddateien , Befehle , Systemaufrufe , Dateiberechtigungen

  1. Konventionell beginnt jeder Prozess mit drei geöffneten Standarddateien : Standardeingabe, Standardausgabe und Standardfehler, verknüpft mit den Dateideskriptoren 0, 1 und 2.
  • Die Standardeingabe ist normalerweise mit Ihrer Tastatur verbunden. Was auch immer Sie eingeben, geht an das Programm.
  • Die Standardausgabe ist normalerweise mit Ihrem Bildschirm verbunden. Sichtbar wird, was das Programm ausgibt.
  • Standardfehler sind normalerweise auch mit Ihrem Bildschirm verbunden.
  • Mit der Shell können Sie Programme aufrufen, sodass die Standardausgabe eines Programms direkt mit der Standardeingabe eines anderen Programms verknüpft („piped“) wird:
    ls | Toilette

Sie können die Shell verwenden, um Programme aufzurufen, sodass die Standardeingabe und/oder -ausgabe mit einer Datei verknüpft wird:
ls > lsOutFile

wc < lsOutFile

  • sort -u <largeFile > sortiertFile
  • Im Allgemeinen ist es den Programmen egal, ob die Shell die Bedeutung ihrer Standarddateien geändert hat.
  1. Unix-Befehle
  • Befehle sind lediglich die Namen ausführbarer Dateien. Die Umgebungsvariable PATH teilt der Shell mit, wo sie nach ihnen suchen soll. Normalerweise hat diese Variable einen Wert wie /bin:/usr/bin:/usr/local/bin:. .
  • Um zu sehen, wo die Shell ein bestimmtes Programm, zum Beispiel vim , findet , sagen Sie where vim .
  1. Systemaufrufe und Bibliotheksaufrufe folgen einigen wichtigen Konventionen.
  • Der Rückgabewert des Aufrufs gibt normalerweise an, ob der Aufruf erfolgreich war (normalerweise ist der Wert 0 oder positiv) oder fehlgeschlagen (normalerweise ist der Wert -1).

Überprüfen Sie immer den Rückgabewert von Bibliotheksaufrufen. Wenn ein Systemaufruf fehlschlägt, kann die Funktion perror() den Fehler ausdrucken (als Standardfehler):
int fd;

char *filename = "myfile";

if ((fd = open(filename, O_RDONLY)) < 0) [

perror(Dateiname); // könnte „meine Datei: Keine solche Datei oder kein solches Verzeichnis“ ausgeben

  • [
  • Eine Handbuchseite für einen Systemaufruf oder eine Bibliotheksroutine listet möglicherweise einen Datentyp auf, den sie nicht definiert, z. B. size_t oder time_t oder O_RDONLY . Diese Typen werden normalerweise in Header-Dateien definiert, die in der Handbuchseite erwähnt werden; Sie müssen alle diese Header-Dateien in Ihr C-Programm einbinden.
  1. Dateiberechtigungen werden unter Unix normalerweise mit Oktalzahlen ausgedrückt.
  • Im obigen Beispiel von creat() ist 0660 eine Oktalzahl (das bedeutet die führende 0), die binär 110.110.000 darstellt. Diese Oktalzahl gewährt dem Dateieigentümer und der Dateigruppe Lese- und Schreibberechtigungen, jedoch keine Ausführungsberechtigungen, anderen Benutzern jedoch keine Berechtigungen.
  • Sie legen Berechtigungen fest, wenn Sie eine Datei über den Parameter des creat()- Aufrufs erstellen.
  • Der Befehl ls -l zeigt Ihnen die Berechtigungen von Dateien an.
  • Sie können die Berechtigungen einer Datei, deren Eigentümer Sie sind, mithilfe des Programms chmod ändern .
  • Alle Ihre Prozesse haben ein Merkmal namens umask, das normalerweise als Oktalzahl dargestellt wird. Wenn ein Prozess eine Datei erstellt, werden die Bits in der umask aus den im creat()- Aufruf angegebenen Berechtigungen entfernt . Wenn Ihre umask also 066 ist, können andere die von Ihnen erstellten Dateien nicht lesen oder schreiben, da 066 Lese- und Schreibberechtigungen für Ihre Gruppe und andere Personen darstellt. Sie können Ihre umask überprüfen und ändern, indem Sie das umask- Programm verwenden , das Sie normalerweise in Ihrem Shell-Startskript aufrufen (je nach Shell ~/.login oder ~/.profile ).

Software-Entwicklungstools

Texteditor , Debugger , Compiler , Handbuchseiten , Erstellen , Suchen ,

  1. Verwenden Sie einen Texteditor, um Ihr Programm zu erstellen, zu ändern und zu überprüfen. Es stehen mehrere vernünftige Texteditoren zur Verfügung.
  • Das Erlernen des vim- Editors und seiner grafischen Benutzeroberfläche, gvim , erfordert etwas Aufwand, bietet aber einen sehr hochwertigen Satz an Werkzeugen zum Bearbeiten von Programmdateien, einschließlich Syntaxhervorhebung, Klammerabgleich, Wortvervollständigung, automatische Einrückung und Suche nach Tags (die verschoben werden). (Sie gelangen schnell von einer Stelle, an der das Programm eine Funktion aufruft, zu der Stelle, an der die Funktion definiert ist) und eine integrierte Handbuchseitensuche. Vim ist für die Verwendung mit der Tastatur konzipiert; Sie müssen die Maus nie benutzen, wenn Sie dies nicht möchten. Es ist für Unix-, Win32- und Microsoft-Betriebssysteme frei verfügbar. Es ist die am weitesten entwickelte Version der Editor-Reihe, die ed , ex , vi und enthältElvis . Sie können die Online-Dokumentation für vim lesen und über den Befehl :help von vim sofortige Hilfe erhalten .
  • Der Emacs- Editor ist eher funktionsreich als vim . Auch das Lernen erfordert erhebliche Anstrengungen. Es ist außerdem sowohl für Unix- als auch für Microsoft-Betriebssysteme frei verfügbar. Dokumentation finden Sie hier .
  • Es gibt viele andere Texteditoren, aber im Allgemeinen bieten sie Ihnen nicht die beiden nützlichsten Funktionen, die Sie zum Erstellen von Programmen benötigen: automatische Einrückung und Syntaxhervorhebung. Allerdings haben diese Texteditoren oft den Vorteil, dass sie entsprechend ihrer begrenzten Fähigkeiten einfacher zu erlernen sind. Zu diesen Texteditoren mit geringerer Qualität gehören (für Unix) pico , gedit und joe sowie (für Microsoft) notepad und word .
  • Möglicherweise sind Sie mit einer integrierten Entwicklungsumgebung (IDE) wie Eclipse, Code Warrior oder .NET vertraut. Diese Umgebungen verfügen im Allgemeinen über Texteditoren, die in Debugger und Compiler integriert sind. Wenn Sie eine solche IDE verwenden, ist es sinnvoll, die zugehörigen Texteditoren zu verwenden.
  1. gdb ist ein Debugger , der Ihre Variablen und Programmstruktur versteht.
  • Dokumentation finden Sie hier .
  • Um gdb effektiv nutzen zu können, müssen Sie das Flag -g an den C- oder C++-Compiler übergeben.
  • Wenn Ihr Programm myProgram fehlgeschlagen ist und eine Datei namens core zurücklässt , versuchen Sie es mit gdb myProgram core .
  • Sie können Ihr Programm auch von Anfang an unter der Kontrolle von gdb ausführen : gdb myProgram .
  • Alle Befehle an gdb können mit einem eindeutigen Präfix abgekürzt werden.
  • Der Hilfebefehl ist sehr nützlich.
  • Der Befehl „where“ zeigt den Aufrufstapel an, einschließlich der Zeilennummern, aus denen hervorgeht, wo sich die einzelnen Routinen befinden. Dies ist der erste Befehl, den Sie beim Debuggen einer Kerndatei ausprobieren sollten.
  • Um den Wert eines Ausdrucks auszugeben (Sie können Ihre Variablen und die üblichen C-Operatoren einschließen), geben Sie print expression ein , wie in
    print (myInt + 59) & 0444;
  • Um Ihr Programm anzuzeigen, versuchen Sie es mit list myFunction oder list myFile.c:38 .
  • Um einen anderen Aktivierungsdatensatz als aktuell festzulegen, verwenden Sie den Befehl up (für neuer) oder down (für weniger aktuell).
  • Sie können in jeder Zeile jeder Datei einen Haltepunkt setzen. Sie können beispielsweise break foo.p:38 sagen, um einen Haltepunkt in Zeile 38 in der Datei foo.p festzulegen . Jedes Mal, wenn Ihr Programm während der Ausführung auf diese Zeile trifft, wird es angehalten und gdb fordert Sie zur Eingabe von Befehlen auf. Sie können sich beispielsweise Variablen ansehen oder das Programm schrittweise durchlaufen.
  • Der nächste Befehl geht eine Anweisung weiter (ruft bei Bedarf beliebige Prozeduren auf und kehrt zurück).
  • Der Befehl „step“ geht eine Anweisung vorwärts, wenn die Anweisung jedoch einen Prozeduraufruf beinhaltet, springt er in die Prozedur und stoppt dort bei der ersten Anweisung.
  • Wenn Sie den Befehl set follow-fork-mode child eingeben, führt gdb beim Ausführen des fork()- Aufrufs weiterhin das Debuggen des untergeordneten Elements durch, nicht des übergeordneten Elements.
  • Verlassen Sie gdb , indem Sie den Befehl quit eingeben .
  • Möglicherweise bevorzugen Sie die Verwendung des grafischen Frontends ddd für gdb .
  1. Geben Sie den Compilerprogrammen gcc oder g ++ immer das Flag -Wall , um eine hohe Warnstufe zu aktivieren. Geben Sie javac in ähnlicher Weise das Flag -Xlint:all . Geben Sie kein Programm ab, das zur Kompilierungszeit Warnungen generiert.
  2. Sie können das Handbuch lesen , um Details zu Programmen, C-Bibliotheksroutinen und Unix-Systemaufrufen zu erhalten, indem Sie das man- Programm verwenden, wie in man printf oder man gcc .
  • Manchmal befindet sich die gewünschte Funktion in einem bestimmten Abschnitt des Unix-Handbuchs und Sie müssen sie explizit anfordern: man 2 open oder man 3 printf . Abschnitt 1 befasst sich mit Programmen, Abschnitt 2 mit Systemaufrufen, Abschnitt 3 mit der C-Bibliothek und Abschnitt 8 mit der Systemverwaltung. Die anderen Abschnitte benötigen Sie höchstwahrscheinlich nicht.
  • Sie können herausfinden, ob ein Programm, eine C-Bibliotheksroutine oder ein Unix-Systemaufruf für ein bestimmtes Thema relevant ist, indem Sie das Flag -k verwenden , wie in man -k print .
  1. Verwenden Sie das Make- Programm, um Rezepte für die Neukompilierung und Neuverknüpfung Ihres Programms zu organisieren, wenn Sie eine Quelldatei ändern.

Wenn Ihr Programm aus mehreren Dateien besteht, können Sie diese separat kompilieren und dann miteinander verknüpfen. Sie kompilieren mit dem Flag -c und verwenden das Flag -o , um die Ausgabedatei anzugeben. Ein sinnvolles Makefile könnte so aussehen:
SOURCES = Treiber.c Eingabe.c Ausgabe.c

OBJEKTE = Treiber.o Eingabe.o Ausgabe.o

HEADERS = common.h

CFLAGS = -g -Wall

Programm: $(OBJECTS)

$(CC) $(CFLAGS) $(OBJECTS) -o Programm

$(OBJEKTE): $(HEADER)

testRun: Programm

  • Programm < testData
    Dieses Makefile verwendet eine integrierte CC- Definition und eine integrierte Regel, um C-Quelldateien wie „driver.c“ in ihre Objektdatei zu konvertieren. Wenn Sie nur input.c ändern , veranlasst make testRun den Compiler, input.o neu zu erstellen , den Compiler dann dazu zu veranlassen, die Objekte neu zu verknüpfen, program zu erstellen und dann program mit der aus der Datei testData umgeleiteten Standardeingabe auszuführen .
  • Wenn Sie viele Quelldateien und viele Header-Dateien haben, möchten Sie möglicherweise das Programm makedepend verwenden , um automatisch die Makefile- Regeln zu erstellen, die angeben, wie Quelldateien von Header-Dateien abhängen. Im obigen Beispiel wird davon ausgegangen, dass alle Quelldateien von allen Headerdateien abhängen, was häufig nicht der Fall ist.
  1. Das grep- Programm kann schnell nach einer Definition oder Variablen suchen , insbesondere in Include-Dateien:
    grep "struct timeval [" /usr/include/*/*.h

Übungen

Machen Sie diese Übungen in C.

  1. Schreiben Sie ein Programm namens atoi , das eine in der Befehlszeile benannte Datendatei öffnet und daraus eine einzelne Eingabezeile liest, die eine in ASCII-Zeichen dargestellte Ganzzahl enthalten sollte. Das Programm wandelt diese Zeichenfolge in eine Ganzzahl um, multipliziert die Ganzzahl mit 3 und gibt das Ergebnis in der Standardausgabe aus. Das Programm darf die Funktion atoi() nicht verwenden . Sie sollten das Make- Programm verwenden. Ihr Makefile sollte drei Regeln haben: atoi , run (wodurch Ihr Programm mit Ihren Standardtestdaten ausgeführt wird und die Ausgabe in eine neue Datei umgeleitet wird) und clean(wodurch temporäre Dateien entfernt werden). Stellen Sie sicher, dass Ihr Programm bei fehlerhaften Daten ordnungsgemäß ausgeführt wird und mit einer hilfreichen Meldung beendet wird, wenn die Datendatei fehlt oder nicht lesbar ist. Gehen Sie Ihr Programm schrittweise durch, indem Sie es mit gdb starten , einen Haltepunkt auf main() setzen und den Befehl „step“ wiederholt verwenden.
  2. Schlagen Sie die Handbuchseite für das Cat -Programm nach. Codieren Sie Ihre eigene Version von cat . Ihre Version muss mehrere (oder keine) Dateinamenparameter akzeptieren. Es müssen keine Optionsparameter akzeptiert werden.
  3. Schreiben Sie ein Programm „removeSuffix“ , das einen einzelnen Parameter akzeptiert: den Namen einer Suffixdatei. Die Suffixdatei enthält eine Zeile pro Eintrag. Ein Eintrag ist eine nicht leere Zeichenfolge, die wir Suffix nennen , gefolgt vom >-Zeichen, gefolgt von einer weiteren (möglicherweise leeren) Zeichenfolge, die wir Ersatz nennen . Ihr Programm sollte alle Suffixe und ihre Ersetzungen in einer Hash-Tabelle speichern. Verwenden Sie eine externe Verkettung. Ihr Programm sollte dann die Standardeingabe lesen. Suchen Sie für jedes durch Leerzeichen getrennte Wort w in der Eingabe das längste Suffix s , das in w vorkommt , und ändern Sie w , indem Sie s entfernen und das einfügens 's Ersatz, wodurch w' entsteht . Geben Sie eine Zeile pro geändertem Wort in der Form w>w' aus . Geben Sie kein Wort aus, das nicht geändert wurde.
Article posted on:25 Aug 2023