Pointer auf struct verhalten von gcc und clang

ath0

Well-Known Member
Hallo @all

ich bin gerade sehr verwirrt. Ich baue gerade wieder etwas c, dabei habe ich bemerkt, dass gcc und clang sich unterschiedlich verhalten.
Gegeben sei dieses simple Beispiel

Code:
#include <stdio.h>
#include <stdlib.h>

struct test{
	struct test *next;
	char val;
	struct test *prev;
};

typedef struct test kst;

void init(kst *tst){
	tst = (kst *) calloc(1, sizeof(kst));
}

int append (kst *tst){
	tst->next = (kst *) calloc(1, sizeof(kst));
}

int main()
{
	kst *fu;
	
	init(fu);
	
	fu->val = 'x';
	printf("%c", fu->val);

	append(fu);
	fu->next->val = 'Y';
	printf("%c", fu->next->val);
	return 0;
}

Übersetze ich das ganze mit clang, ist fu nicht initialisiert, also zeigt der Pointer auf eine Adresse die es nicht gibt. Das verhalten finde ich richtig. Wenn ich das ganze dann durch die Function init jage, bekomme ich eine gültige Adresse für fu.

Übersetze ich mit gcc, ist fu irgendwie halb initialisiert. nach der Funktion init hat fu aber wieder wie vor dem Aufruf diesen halbgaren zustand. Hier mal der gdb auszug

Code:
Breakpoint 1, main () at test.c:31
31		init(fu);
(gdb) print fu
$1 = (kst *) 0x40047e
(gdb) s
init (tst=0x40047e) at test.c:13
13		tst = (kst *) calloc(1, sizeof(kst));
(gdb) 
14	}
(gdb) print tst
$2 = (kst *) 0x800c07040
(gdb) print *tst
$3 = {next = 0x0, val = 0 '\0', prev = 0x0}
(gdb) s
main () at test.c:33
33		fu->val = 'x';
(gdb) print fu
$4 = (kst *) 0x40047e
(gdb) print *fu
$5 = {next = 0x35ff00c308c48348, val = -2 '�', prev = 0x25ff00401f0f0020}
(gdb) s

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400695 in main () at test.c:33
33		fu->val = 'x';
(gdb)

Ich bin auf dieses verhalten aufmerksam geworden, weil ich gerade an anderer Stelle das Problem habe, dass mein Pointer, den ich genau wie im obigen Beispiel deklariere und initialisiere, nach der Initialisierung immer noch keinen gültigen wert hat. In diesem Fall übersetze ich jedoch mit clang, das im gezeigten Beispiel ja wie erwartet funktioniert.

Jetzt meine Frage, was mache ich falsch? Oder kann mir einer erklären was da los ist? Ich habe schon gefühlte 10000000 mal genau so einen Pointer übergeben und nach dem Rücksprung das Ergebnis gehabt, welches ich haben wollte. Ich kann mir das echt nicht erklären.
 
Ähm ... wie soll init denn bitte deinen Pointer aktualisieren?

In C++ könntest du eine Referenz auf den Pointer übergeben, dann würde das funktionieren. Aber in C musst du schon einen Pointer of den Pointer verwenden.
 
ebent.
richtig waere z.b. die funktion init so gewesen:
Code:
void init(kst **tst){
	*tst = (kst *) calloc(1, sizeof(kst));
}

und der aufruf dann entsprechend
Code:
init(&fu);
 
Wenn wir schon dabei sind, wann immer es geht const verwenden:
Code:
void init(kst ** const tst){
	*tst = (kst *) calloc(1, sizeof(kst));
}

Wer nicht weiß was das const dort macht sollte sich in die Materie einlesen bis er verstanden hat wofür das gut ist. Erst wenn man so weit ist kann man wirklich die Vorteile aus C/C++ raus ziehen. Vorher verwendet man einfach nur eine Sprache, die es einem Leicht macht sich selbst in den Fuß zu schießen. Wie ath0 das oben ja sehr anschaulich demonstriert hat.
 
Zufall. Gerade das macht C und C++ ja so gefährlich, wenn man noch kein gutes Verständnis der Sprache hat. Oben war es noch recht offensichtlich, aber man kann sich natürlich noch auf viel undeutlichere Art den Hintern wegschießen. Zum Beispiel hatte ich gerade heute in Quake II diesen seit der Erstveröffentlichung 1997 vorhandenen Bug auf dem Tisch: https://github.com/yquake2/yquake2/commit/1dd083cd7d43fc15ddb150597be7a3948e1ffc0b :)
 
Ähm ... wie soll init denn bitte deinen Pointer aktualisieren?
Hmm, das klingt jetzt etwas arrogant. Man beachte bitte die Uhrzeit und passe den Tonfall im Kopf bitte auf schläfrig, verwirrt.

Seltsam nur das es schon sehr oft funktioniert hat ... Danke für eure Hilfe :)
Wenn fu zufällig auf irgendwelchen gültigen Speicher zeigt, zum Beispiel irgendwo weiter hinten im Stack, kann das gehen. Dann leakt mit init() halt etwas Speicher aber append funktioniert.
 
Das sind halt die Nachteile dieser Sprache C, denn Fehler, die bei anderen Sprachen beim Compilerlauf abgeschaltet werden, werden nicht gefunden. Ja und oft treten Fehler erst dann auf, wenn das Programm bereits freigegeben wurde.

Jo und dann hat man den Salat, zum Glück geht man immer mehr weg von dieser Sprache.
 
Jo und dann hat man den Salat, zum Glück geht man immer mehr weg von dieser Sprache.
... und gibt damit eine Menge Vorteile auf.

Das ist tendenziell gut, weil die meisten Programmierer nicht in der Lage sind diese Vorteile zu nutzen. Ich persönlich empfinde sie aber als sehr wertvoll und will nicht darauf verzichten.

Übrigens hätte eine statische Analyse den Fehler sofort hervorgebracht. Ein 8051 Compiler hätte sich wahrscheinlich sofort darüber beschwert, dass tst ein Wert zugewiesen wird, der dann nirgendwo verwendet wird.
 
Ich für meinen Teil, zieh C fast allen Sprachen vor, würde es C nicht geben hätte ich weiter Assembler Programmiert ;)
Wie ein Call by Reference gemacht wird hatte ich vor Ewigkeiten mal in dem Buch c von a bis z gelesen. Da scheint es dann falsch beschrieben gewesen zu sein.

Hmm, das klingt jetzt etwas arrogant. Man beachte bitte die Uhrzeit und passe den Tonfall im Kopf bitte auf schläfrig, verwirrt.
Kein Problem, ich habe dich schon verstanden, ich bin da nicht so. Außerdem habe ich ja schon blumigere Antworten von dir bekommen. :)

Danke nochmal an euch! :)
 
Kein Problem, ich habe dich schon verstanden, ich bin da nicht so. Außerdem habe ich ja schon blumigere Antworten von dir bekommen. :)
Ja, meine Posts lesen sich öfter mal so. Das legt halt an der fehlenden Mimik. Einfach immer daran denken, dass ich mich selbst nicht sonderlich ernst nehme und dazu neige die Dinge zu überspitzen.
 
Wie ein Call by Reference gemacht wird hatte ich vor Ewigkeiten mal in dem Buch c von a bis z gelesen. Da scheint es dann falsch beschrieben gewesen zu sein.

Naja wenn man zu doof ist zu sehen, das man mit dem hier beschriebenem Call by Reference eine Adresse übergibt die Grütze ist, dann hilft auch das beste Buch nix. :ugly:

Nur mal so zum Abschluss des Threads.
 
OT: weiss jemand wie ich rauskriegen kann wo stack und wo heap liegen wenn ich mein programm habe?

abgesehen von einem
Code:
int main(void)
{
  int a;
  int *p;
  p=malloc(10);
  printf("stack %16llx\n",(unsigned long long)(&a));
  printf("heap  %16llx\n",(unsigned long long)(p));
}
natuerlich?
 
Das ist eine stark platformabhängige Frage.

Edit:
Auf x86 ist der Speicher sowieso virtuell, auf die physikalischen Adressen hast du also gar keinen Zugriff. Auf 8051 kann ich dir direkt nach dem Linken die Speicheradresse jeder einzelnen Variable und den Stackpointer verraten.

Bei x86 fängt der Stack ja soweit ich mich entsinne hinten an und wächst nach vorne. Wo hinten ist, weiß ich aber nicht. Wie groß der Stack ist, wird aber vom Linker festgelegt.
 
Zuletzt bearbeitet:
Das virtuelle Memory layout der verschiedenen sections eines Programms kannst du doch mit elfdump, oder objdump sehen:
zB: objdump -h <executable>
Die physikalische Platzierung haengt von sehr vielen Faktoren ab: Art des Mappings, freier Speicherplatz etc. Das ist denke ich kaum vorauszusagen.
 
Zurück
Oben