C Verständnisproblem C

lordg2009

Lt. Commander
Registriert
Apr. 2009
Beiträge
1.503
Hallo

Ich habe gerade ein wenig Zeit über und bin dabei C zu lernen. Bis jetzt fand ich alles logisch, aber bei dem Kapitel über Zeiger habe ich ein Verständnisproblem. Zur Info, ich lerne mit dem freien Buch "C von A bis Z von Jürgen Wolf".

Also folgendes:

Code:
/* Bsp-Programm */
#include <stdio.h>

int main(void) {
    char *string = "Hallo Welt\n";
}

So wie ich es verstanden habe ist string hier der Zeiger, der als Wert eine Adresse vom Arbeitsspeicher enthält. In dem Fall die Startadresse des Arrays, in dem Buchstabe für Buchstabe der String gespeichert ist. Da der Zeiger und die Variable bzw. Array auf den er weist immer den gleichen Datentyp haben, nehme ich also an, dass der Array in diesem Fall auch vom Typ char ist.
Was mich jetzt wunder ist, dass dieser Array nicht initialiesiert werden musste, so wie:

Code:
/* Bsp-Programm */
#include <stdio.h>

int main(void) {
    char string[12];
    char *string = "Hallo Welt\n";
}

Also nehme ich an, dass dieser Schritt automatisch passiert und dass der Speicher, der reserviert wird der Stringlänge + 1 entspricht. Ist das richtig?

Wenn ja, dann verwundert es mich, da doch sonst bei C immer alles manuell initialiesiert werden muss.
 
Strikt genommen ist der Code, den du da im 1. Beispiel gepostet hast, meines Erachtens nicht 100% korrekt. "Hallo Welt\n" ist ein sogenanntes String-Literal. Der Compiler legt den in einem read-only-Speicherbereich des Programms ab. Deshalb darf / kann das natürlich auch nicht modifiziert werden; es ist also konstant. Daher müßte der Code eigentlich so aussehen.

Code:
/* Bsp-Programm */
#include <stdio.h>

int main(void) {
    const char *string = "Hallo Welt\n";

    return 0; /* <- Bin mir im Moment nicht sicher, ob das bei C Pflicht ist. In C++ kann man es weglassen. */
}
 
Zuletzt bearbeitet: (Sch*** Rechtschreibung ...)
Die Deklaration machst du doch afaik

char * ...

in C kann man lesbaren Code schreiben und sehr verschachtelten Code.

Hier würde ich sagen, hat der Autor einfach mal die Deklaration und Wertzuweisung gekürzt und in eine Code-Zeile gepackt.
 
lordg2009 schrieb:
Wenn ja, dann verwundert es mich, da doch sonst bei C immer alles manuell initialiesiert werden muss.

Literale sind da eben eine Ausnahme. Das fällt in die Zuständigkeit des Compilers.
 
Also in C ist alles, was in "" steht ein String-Literal. Bedeutet, es hat den Typ const char[Länge+1].
Mit char*string="Hallo Welt\n" weißt du dem Zeiger string die Speicheradresse des Literals "Hallo Welt\n" zu. C wandelt dabei automatisch den Typ von const char[12] in char* um. Das ist, wie antred schon geschrieben hat, nicht so gut, weil du jetzt möglicherweise in einen readonly-Speicherbereich schreibst/schreiben kannst.
Dein zweites Beispiel funktioniert nicht, da:
1. du nicht zwei Variablen mit gleichem Namen anlegen darfst.
2. (Annahme: char in Z.6 weg) du nicht Hallo Welt in dein angelegten Speicher kopierst, sondern den Zeiger auf diesen Speicher umbiegst. Falls du kopieren wolltests, hättest du ein strcpy(_s) machen müssen.
 
_killy_ schrieb:
Die Deklaration machst du doch afaik

char * ...

in C kann man lesbaren Code schreiben und sehr verschachtelten Code.

Hier würde ich sagen, hat der Autor einfach mal die Deklaration und Wertzuweisung gekürzt und in eine Code-Zeile gepackt.

Das hat zwar mit der Frage des Threaderstellers absolut überhaupt nichts zu tun, aber daß man Deklaration und Wertzuweisung bei lokalen Variablen getrennt vornehmen mußte, ist Schnee von gestern. Heute muß man das nicht mehr, und so wie ich es sehe, sollte man es auch nicht tun. Wozu eine unnötige Fehlerquelle (nicht initialisierte Variablen) einbauen?
 
antred schrieb:
Literale sind da eben eine Ausnahme. Das fällt in die Zuständigkeit des Compilers.

Stimmt nicht. Der Zeiger wurde mit der Adresse des Strings initialisiert. Völlig normaler Vorgang und keine Ausnahme irgendeiner Art.
 
asdfman schrieb:
Stimmt nicht. Der Zeiger wurde mit der Adresse des Strings initialisiert. Völlig normaler Vorgang und keine Ausnahme irgendeiner Art.

Es ging hier ganz offensichtlich um das Literal selbst und um die Tatsache, daß er als Programmierer sich nicht darum kümmern muß, für das Literal Speicherplatz zur Verfügung zu stellen.
 
Die Antwort von antred war bestens. Der Platz und die Adresse für das String-Lateral wird durch den Compiler festgelegt, i.d.R. Read-Only (ist vor allem bei Microcontroller-Programmierung wichtig zu wissen). Mit dem char* holst du dir einfach nur die Adresse von dem Teil, das hast du schon gut erkannt.

bei char[12] wird jedoch Speicher auf dem Stack "alloziert".
 
@antred

Genau darum ging es mir. Alle möglichen Variablen müssen ihren Speicherbereich erst reserviert bekommen und beim Stringliteral ging das automatisch. Ich meine in der Zeiel selbst habe ich doch nur einen Zeiger initialisiert und deklariert, aber zusätzlich wird automatisch der Bereich, in dem der string-array gespeichert wird, initialisiert.

Die Adresse des Zeigers wird anscheinend auch ganz woanders initialisiert, als das Array, auf den der Zeiger dann zeigt. Und genau dieses Array wir eben automatisch mit initialisiert.
 
Nur zum Verständnis: Variablen landen in der Regel auf dem Stack, das bestimmt auch der compiler. So z.B. auch "char* string", das sind 4 Byte (bzw 8 Byte bei 64 Bit) im Stack.

Um das alles noch besser zu verstehen wäre es noch gut, den Aufbau des Maschinen-Codes bzw. der EXE zu kennen. Dort gibts unter anderem ein "data segment" welches vom EDX-Register referenziert wird. In diesem Bereich landen alle String-Laterale und Konstanten. So kann der compiler z.B. auch ein char[12] dort platzieren wenn es nicht in den Stack passt.
 
Wie kann man sich den Maschinen-Code denn ansehen? Man kann ja eine exe nicht mit dem Texteditor öffnen.

und noch ne Frage: Wenn ich eine Datei vom rein geschrieben C-Code mit dem compiler zur .exe umwandle, dann wird die Datei:

1) Durch den Präprozessor gejagt -> Anweisungen hinter der # werden bearbeitet und Kommentare entfernt
2) Danach compiled -> keine Ahnung was da passiert
3) in assembler umgeschrieben
4) gelinkt -> Die Dateien *.h werden mit dem Hauptcode zusammengefügt

ungefähr richtige Vorstellung? Kommt mir nähmlich alles noch komisch vor.
 
1. richtig, dort werden auch die .h eingebunden.
2. und 3. passiert normalerweise in einem, da kommen die .obj Dateien raus. Wenn du eine Funktion aus einer anderen .c Datei verwendest, wird die Adresse der Funktion als Platzhalter vermerkt.
4. linken: Die .obj-Dateien werden zusammengebaut. Dabei werden die Platzhalten aufgelöst. Falls das nicht geht: Linkerfehler (unresolved reference my_function in my_file.obj).
 
lordg2009 schrieb:
Wie kann man sich den Maschinen-Code denn ansehen? Man kann ja eine exe nicht mit dem Texteditor öffnen.
Entweder, du beendest den Compiler, bevor er den Assembler aufruft, oder du verwendest einen Disassembler.

€: Für die erste Methode kann man GCC die Option -S (oder -s weiß nicht mehr genau) übergeben.

und noch ne Frage: Wenn ich eine Datei vom rein geschrieben C-Code mit dem compiler zur .exe umwandle, dann wird die Datei:

1) Durch den Präprozessor gejagt -> Anweisungen hinter der # werden bearbeitet und Kommentare entfernt
2) Danach compiled -> keine Ahnung was da passiert
3) in assembler umgeschrieben
4) gelinkt -> Die Dateien *.h werden mit dem Hauptcode zusammengefügt

ungefähr richtige Vorstellung? Kommt mir nähmlich alles noch komisch vor.

1) Der Präprozessor macht noch andere Dinge. Aber im Grunde ersetzt er bestehenden Text durch vorgegebenen Neuen.
2) Magie!
3) Wenn man will, ist Assember das, was der Compiler am Ende ausspuckt.
4) Nein. Das verstehst du falsch. Headerdateien werden vom Präprozessor expandiert.
Der Linker setzt alle einzeln kompilierten Dateien zu einem fertigen Binary zusammen.
Wenn du also 12 .c Dateien hast, erzeugt der Compiler daraus 12 Objektdateien. Die
enthalten schon Maschinencode, aber sind so nicht ausführbar. Dafür sorgt der Linker.
 
Zuletzt bearbeitet:
Zurück
Oben