Zeiger in C
-
@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?
-
@john-0 Ja aber du führst den impliziten Cast in Zeile 5 durch und hast eigentlich nichts dadurch gewonnen. Diese Regel nur Zeiger auf void zu übergeben kann man eigentlich echt streichen und deshalb gibt es da auch nur eine Warnung, die auch noch mit pedantic rausgeholt werden muss.
-
@Peter-Viehweger sagte in Zeiger in C:
@john-0 Ja aber du führst den impliziten Cast in Zeile 5 durch
Nein, das ist eine Zuweisung und eben kein Cast. Insbesondere C Casts sind ein großes Problem, weil man da ganz schnell vollkommen inkompatibel Dinge einander zuweisen kann.
#include <stdio.h> #include <stdlib.h> int main () { size_t size = 10000; printf ("%p\n", (void*)size); return EXIT_SUCCESS; }
-
@john-0 https://de.wikibooks.org/wiki/C-Programmierung:_Typumwandlung
Das gilt nicht nur für int und float, sondern auch für void * und float *
-
@Peter-Viehweger Es geht darum, dass man so programmiert, dass Programme unabhängig vom verwendeten Compiler bzw. der Compilerversion sich übersetzen lassen. D.h. normkonforme Programme werden auch in Zukunft mit hoher Wahrscheinlichkeit ohne Änderungen übersetzbar bleiben und das gleiche Verhalten aufweisen. Wenn man anfängt irgend eine ähnliche Sprache wie in der betreffenden Norm zu verwenden läuft man Gefahr, dass dies in Zukunft nicht mehr der Fall sein wird. Mich ärgert das, dass Compiler nicht gleich mit allen Flags aktiviert übersetzen, und man diese explizit anschalten muss. Sinnvoller wäre es, dass man die Normprüfung mit Flags stufenweise abschalten kann.
Zu dem konkreten Verhalten in C: Natürlich findet an dieser Stelle eine Zuweisung mit Typumwandlung statt, aber es war noch nie eine gute Idee hier entweder auf einen Casts zu setzen (das hebelt die Typprüfung faktisch aus) oder einfach so zu tun, als ob die Formatstringprüfung eine lästige Petitesse wäre. Es gibt Exploits die Formatstringfehler ausnutzen, und deshalb ist so extrem wichtig hier diese Fehlermeldungen eben nicht zu ignorieren.
-
@Peter-Viehweger sagte in Zeiger in C:
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.
Du bist hier im C-Forum. Im Standard-C-Forum.
@Peter-Viehweger sagte in Zeiger in C:
n = read(fd, &f, sizeof(f));
@Peter-Viehweger sagte in Zeiger in C:
Muss ja eigentlich auch so sein, oder?
Das ist ja auch keine Funktion mit einer Ellipse.
ISO/IEC 9899:202x (E) § 6.5.2.2/7:
If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. [...]
-
@Peter-Viehweger
Es macht einen Unterschied ob du etwas an eine "normale" Funktion oder an eine "varargs" Funktion (so eine mit ", ...") im "varargs" Teil übergibst.Bei der Übergabe an eine normale Funktion wird z.B. auch eine Konvertierung
long
->int
oderlong long
->int
implizit durchgeführt.Bei der Übergabe im varargs Teil nicht. Und das kann dann zu echten Fehlern führen.
Beispiel:#include <stdio.h> void int_fun(int i) {} int main() { long long ll = 42; int_fun(ll); // OK printf("%d, %d\n", ll, ll); // falsch return 0; } // Übliche Ausgabe auf 32 Bit Systemen: 42, 0
Da braucht man dann auch kein
-pedantic
mehr um eine Warning zu bekommen.<source>:10:11: warning: format '%d' expects argument of type 'int', but argument 2 has type 'long long int' [-Wformat=] 10 | printf("%d, %d\n", ll, ll); | ~^ ~~ | | | | int long long int | %lld <source>:10:15: warning: format '%d' expects argument of type 'int', but argument 3 has type 'long long int' [-Wformat=] 10 | printf("%d, %d\n", ll, ll); | ~^ ~~ | | | | int long long int | %lld
%p
und nicht-void Zeiger ist hier ein Sonderfall, weil die Repräsentation von Zeigern im Speicher bzw. Registern üblicherweise für alle Zeiger gleich ist. D.h. es wird üblicherweise "trotzdem funktionieren". Das weiss der Compiler, und daher gibt er dir nur eine Warning mit-pedantic
. Das heisst aber nicht dass es richtig ist.
-
@Peter-Viehweger sagte in Zeiger in C:
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.
Den muss ich mir merken. "Aaaaber...es gibt nur warnungen wenn man sie auch anmacht!!11"
-
@hustbaer sagte in Zeiger in C:
Da braucht man dann auch kein
-pedantic
mehr um eine Warning zu bekommen.
[...]
%p
und nicht-void Zeiger ist hier ein Sonderfall, weil die Repräsentation von Zeigern im Speicher bzw. Registern üblicherweise für alle Zeiger gleich ist. D.h. es wird üblicherweise "trotzdem funktionieren". Das weiss der Compiler, und daher gibt er dir nur eine Warning mit-pedantic
. Das heisst aber nicht dass es richtig ist.Das weiß nicht nur der Compiler, sondern ich auch. Deshalb habe ich ja gesagt, dass es eigentlich Unsinn darstellt und den Code aufpustet, wenn vorher noch nach void umgewandelt wird.
@Cardiac sagte in Zeiger in C:
Den muss ich mir merken. "Aaaaber...es gibt nur warnungen wenn man sie auch anmacht!!11"
Warnungen gibt es auch, wenn man kein -pedantic übergibt, nur die, dass man vorher doch bitte den Zeiger nach void * umwandeln soll, eben nicht......
-
@Peter-Viehweger sagte in Zeiger in C:
Das weiß nicht nur der Compiler, sondern ich auch.
Also erstmal würde ich behaupten dass du viel weniger weisst als du meinst zu wissen. Du hast ja selbst in diesem Thread schon demonstriert dass du eben nicht weisst was bei varargs Funktionen passiert. Siehe
@Peter-Viehweger sagte in Zeiger in C:
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:
Deshalb habe ich ja gesagt, dass es eigentlich Unsinn darstellt und den Code aufpustet, wenn vorher noch nach void umgewandelt wird.
Äh... nein. Klingt für mich eher so als ob du da was falsch verstanden gehabt hättest und es jetzt im Nachhinein schönreden willst. Denn es passiert eben genau kein impliziter Cast, es wird lediglich das Bitmuster vom
float*
alsvoid*
interpretiert. Das ist kein Cast sondern ehermemcpy
. Und wie schon geschrieben wurde eben UB.
Und dann ist halt die Frage ob es gut ist sich auf "funktioniert trotzdem" bei undefiniertem Verhalten zu verlassen. Speziell wenn der einzige Vorteil davon ist dass man einen kleinen (laut Sprachstandard nötigen!) Cast nicht schreiben muss. Das macht mMn. keinen Sinn - sollte man sich nicht angewöhnen.
-
@hustbaer Du hast recht: Ich müsste jetzt im Internet nachlesen, wie das mit den varargs genau funktioniert, weil ich so etwas eigentlich nie benutzt habe, außer eben bei printf und scanf. Wenn ich mich aber richtig erinnere, müssen der Funktion "irgendwie" die Anzahl der Argumente und die jeweilige Größe bekannt gemacht werden.
Prüfungsfrage: Worin besteht bei Zeigern der Unterschied zwischen einem Cast und einer Uminterpretation des Bitmusters?
-
@Peter-Viehweger sagte in Zeiger in C:
Wenn ich mich aber richtig erinnere, müssen der Funktion "irgendwie" die Anzahl der Argumente und die jeweilige Größe bekannt gemacht werden.
Das erfolgt über das erste Argument– dem Formatstring! Deshalb ist es ja so wichtig, da nicht sinnfreie Aktionen durchzuführen und entweder zu casten (es besteht die Gefahr etwas Falsches zu übergeben) oder Warnungen zu ignorieren.
Nachtrag: Ein Beispiel mit funktionierendem Code, was verdeutlicht wie man in C mit Parameter Ellipsen arbeitet.
#include <stdarg.h> #include <stdlib.h> #include <stdio.h> int add_values (size_t num_arguments, ...) { int sum = 0, a = 0; va_list list_pointer; va_start (list_pointer, num_arguments); for (size_t i = 0; i < num_arguments; ++i) { a = va_arg(list_pointer, int); sum += a; } va_end(list_pointer); return sum; } int main () { int a = 1, b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8; printf ("%i\n", add_values(3, a, b, c)); printf ("%i\n", add_values(8, a, b, c, d, e, f, g, h)); return EXIT_SUCCESS; }