Zeiger in C
-
Ich bin gerade dabei das Buch ,,Grundkurs C" von Jürgen Wolf und René Krooß durchzuarbeiten. In der aktuellesten Fassung (Stand 25.12.2020) steht auf Seite 257 ein für mich unbegreiflicher Text, welchen ich mal aufgrund von Urheberrechtsgründen umformuliere und in zusammengefasster Version präsentiere:
Für
float *fptr; float fval1 = 111.111f, fval2; fptr =& fval1; fval2 = *fptr;
wird gesagt, dass der Wert von fval1 durch den Zeiger fptr mit Verwendung des Indirektionsoperators * der Variable fval2 zugewiesen wird. Soweit klar. Aber dann schreibt er: ,,Nun zeigen fval1 und fval2 auf dieselbe Adresse! Das heißt natürlich auch, dass wenn Sie den Wert von val1 ändern, sich damit gleichzeitig der Wert von fval2 ändert. Probieren Sie dies ruhig aus, und Sie sehen, dass Zeiger wirklich nur Verweise in der Variablentabelle sind."
Nun habe ich das ausprobiert und habe entdeckt, dass fval2 eine eigene Adresse hat und sich demnach bei einer Wertänderung von fval2 nicht der Wert von fval1 ändert und umgekehrt. Ich vermute, dass bei der Produktion einfach ein falscher Text unter das falsche Beispiel geschrieben wurde. Oder verstehe ich doch was falsch? Was meint er damit? An sich ist das Buch bisher gut gewesen. Nur diese Stelle macht mich stutzig.
Schöne Grüße und noch frohes Fest
-
@cbeginner sagte in Zeiger in C:
float *fptr;
float fval1 = 111.111f, fval2;
fptr =& fval1;
fval2 = *fptr;Jup, für solchen mangelhaften Code ist Jürgen Wolf bekannt.
Such dir bitte ein anderes Buch.
-
@cbeginner sagte in Zeiger in C:
Nun zeigen fval1 und fval2 auf dieselbe Adresse!
Wie kann denn ein float Wert auf eine Adresse zeigen?
-
@cbeginner
Ok leck, ist der Code grottig. Die Variable fptr ist nicht initialisiert, wird aber mit einem Wert verundet. Das Verhalten ist undefiniert und dürfte zu einem Absturz führen.
-
-
-
@Quiche-Lorraine sagte in Zeiger in C:
Die Variable fptr ist nicht initialisiert, wird aber mit einem Wert verundet.
Nee, ganz so schlecht auch wieder nicht.
-
@Quiche-Lorraine sagte in Zeiger in C:
Danke werde ich mir mal anschauen. Werde das Grundlagenbuch dennoch durchlesen (habe schließlich Geld dafür bezahlt )
-
@Quiche-Lorraine sagte in Zeiger in C:
@cbeginner
Ok leck, ist der Code grottig. Die Variable fptr ist nicht initialisiert, wird aber mit einem Wert verundet. Das Verhalten ist undefiniert und dürfte zu einem Absturz führen.Nein. Der Code ist technisch gesehen korrekt. Das Problem ist die Erklärung
,,Nun zeigen fval1 und fval2 auf dieselbe Adresse! Das heißt natürlich auch, dass wenn Sie den Wert von val1 ändern, sich damit gleichzeitig der Wert von fval2 ändert. Probieren Sie dies ruhig aus, und Sie sehen, dass Zeiger wirklich nur Verweise in der Variablentabelle sind."
Was halt völliger Müll ist, wie cbeginner schon selber festgestellt hat. Und das ist auch kein Produktionsfehler - Jürgen Wolf weiß das wirklich nicht besser.
-
@cbeginner sagte in Zeiger in C:
Danke werde ich mir mal anschauen. Werde das Grundlagenbuch dennoch durchlesen (habe schließlich Geld dafür bezahlt )
Nein! Tu das nicht! Jürgen Wolf ist wirklich so schlecht, dass du (ohne Scherz oder Übertreibung) dümmer wirst, wenn du das liest. Da ist noch mehr solcher Stuss in den Büchern, wie dieser hier. Und wie sollst du das als Anfänger erkennen, wenn du nicht rigoros alles hinterfragst? Und wenn du alles so rigoros hinterfragst, dann brauchst du das Lehrbuch nicht. Und ich rede nicht nur von objektiv überprüfbaren Fakten, die falsch sind. Du darfst auch davon ausgehen, dass eine Menge von dem Stil und Anekdoten in dem Buch nicht hilfreich sind, und das kannst du dann ohne langjährige eigene Erfahrung gar nicht selber als solches erkennen.
Der Punkt ist jedenfalls, dass da so viel falsches drin steht, dass du dir fast sicher ein paar falsche Sachen angewöhnen wirst, und die wird man nur schwer wieder los. Und das was du da vielleicht doch noch an Richtigem rausziehen kannst (Mit viel Mühe!), das bekommst du anderswo besser.
-
@cbeginner sagte in Zeiger in C:
,,Grundkurs C" von Jürgen Wolf und René Krooß
Wir sollten uns das Ding besorgen, auseinandernehmen und die Ergebnisse dem Rheinwerk Verlag stecken!
-
-
@cbeginner sagte in Zeiger in C:
float *fptr; float fval1 = 111.111f, fval2; fptr =& fval1; fval2 = *fptr;
Was der Autor beschreibt sieht man in folgenden Code, aber dazu braucht man zwei Zeiger, die auf zuerst auf unterschiedliche Variablen verweisen und dann auf dieselbe.
#include <stdio.h> #include <stdlib.h> void print_vars (float var1, float var2, float *ptr1, float *ptr2) { void *p1 = ptr1, *p2 = ptr2; // leider notwendig für Ausgabe, da printf nur void* und nicht float* akzeptiert. printf ("%.3f, %.3f, %.3f, %.3f, p1=%p, p2=%p\n", var1, var2, *ptr1, *ptr2, p1, p2); } int main () { float fval1 = 1.0f, fval2 = 2.0f; float *fptr1 = &fval1, *fptr2 = &fval2; print_vars (fval1, fval2, fptr1, fptr2); fptr2 = &fval1; print_vars (fval1, fval2, fptr1, fptr2); return EXIT_SUCCESS; }
Damit man sieht was im Code des Autoren passiert, habe ich die Ausgabefunktion kopiert, und dann den Code leicht modifiziert
#include <stdio.h> #include <stdlib.h> void print_vars (float var1, float var2, float *ptr1, float *ptr2) { void *p1 = ptr1, *p2 = ptr2; // leider notwendig für Ausgabe, da printf nur void* und nicht float* akzeptiert. printf ("%.3f, %.3f, %.3f, %.3f, p1=%p, p2=%p\n", var1, var2, *ptr1, *ptr2, p1, p2); } int main () { // die beiden Zeilen dienen nur dazu, dass die exakt selbe Funktion print_vars genutzt werden kann float zero = 0.0f; float *fptr2 = &zero; // Der Code ist etwas modifiziert. // Anstatt fptr heisst die Pointer-Variable fptr1. // fval2 wird mit einem Wert 2.0 initialisiert und fval1 bekommt den Wert 1.0 float *fptr1; float fval1 = 1.0f, fval2 = 2.0f; fptr1 = &fval1; print_vars (fval1, fval2, fptr1, fptr2); fval2 = *fptr1; print_vars (fval1, fval2, fptr1, fptr2); return EXIT_SUCCESS; }
-
@john-0 sagte in Zeiger in C:
void *p1 = ptr1, *p2 = ptr2; // leider notwendig für Ausgabe, da printf nur void* und nicht float* akzeptiert.
-
@Peter-Viehweger ISO/IEC 9899:202x (E) § 7.21.6.1/8:
p
The argument shall be a pointer tovoid
. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.@john-0 Ein cast hätt's auch getan.
-
Der Compiler führt aber den impliziten Cast zu "void *" aus, weshalb so etwas nur unnötig den Code aufpustet.
-
@Peter-Viehweger sagte in Zeiger in C:
Der Compiler führt aber den impliziten Cast zu "void *" aus, [...]
ISO/IEC 9899:202x (E) § 6.5.2.2:
§ 6.5.2.2/7: [...] The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.
§ 6.5.2.2/6: If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. [...]
Nix magically
float*
tovoid*
. Verbreite bitte mit Halbwissen keinen Unsinn und geh' mir aus der Sonne. Danke.
-
int main() { float f = 0.0; printf("%p\n", &f); printf("%p\n", (void *) &f); getchar(); return 0; }
00000027A76FFA20 00000027A76FFA20
Keine Fehler, keine Warnungen, keine Unterschiede im Programmablauf. Was für Probleme sollen denn deiner Meinung nach auftreten?
-
@Peter-Viehweger Ich habe dir oben zitiert was der Standard sagt. Punkt.
Und zu deiner mein-compiler-meckert-nicht-Theorie:
Swordfish@Neptune:/mnt/c/Users/Swordfish> tail foo.c #include <stdio.h> int main(void) { float pi = 3.1415f; printf("%p\n", &pi); } Swordfish@Neptune:/mnt/c/Users/Swordfish> gcc -Wall -Wextra -pedantic-errors foo.c -o foo foo.c: In function ‘main’: foo.c:6:11: warning: format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘float *’ [-Wformat=] printf("%p\n", &pi); ~^ ~~~ Swordfish@Neptune:/mnt/c/Users/Swordfish> clang -Wall -Wextra -pedantic foo.c -o foo foo.c:6:17: warning: format specifies type 'void *' but the argument has type 'float *' [-Wformat-pedantic] printf("%p\n", &pi); ~~ ^~~ 1 warning generated. Swordfish@Neptune:/mnt/c/Users/Swordfish>
-
@Swordfish sagte in Zeiger in C:
@john-0 Ein cast hätt's auch getan.
Ich caste nicht, wenn es ohne geht, weil Casts schnell echte Fehler verdecken.
Nachtrag @Peter-Viehweger: Ich habe die beiden Programme mit
gcc -std=c11 -pedantic -Wall -Wextra
übersetzt, der Unterschied zu C17 ist gering, da die Änderungen auch in der C11 Version eingepflegt wurden, d.h. auch mit-std=c17
wird das Programm gleich übersetzt.
-
Ja man kann immer Warnungen aus dem Compiler "herauskitzeln". Die Warnung wird aber nur deshalb ausgegeben, weil mit -pedantic auf Standardkonformität geprüft wird und der Standard - wie du bereits erläutert hast - besagt, dass an printf beim Spezifizierer "%p" ein "void *" übergeben werden sollte. Das ist aber auch der einzige Grund.
Hier gibt es keine Warnung, obwohl dort ja auch ein "float *" übergeben wird, wo "void *" erwartet wird:
#include <unistd.h> #include <fcntl.h> int main() { float f; int fd; int n; int rc; fd = open("/dev/random", O_RDONLY); if(fd == -1) { return 1; } n = read(fd, &f, sizeof(f)); if(n != sizeof(f)) { return 1; } rc = close(fd); if(rc == -1) { return 1; } return 0; }
[root@FreeBSD ~]# cc -o main -Wall -Wextra -pedantic main.c [root@FreeBSD ~]# gcc -o main -Wall -Wextra -pedantic main.c [root@FreeBSD ~]#
Muss ja eigentlich auch so sein, oder?