Fühlt sich der gcc hier veralbert?

OP #8072497
Lesenswert?

Guten Morgen,

ist dieser Fehler so dumm, dass der nicht einmal eine Warnung wert ist? Ungefähr 70 -W von -Waggregate-return bis -Wwrite-strings reichen nicht? Kann man dagegen etwas tun?

1
static void
2
foo (const char *bozo_exe)
3
{
4
   char   *args[16], *envs[4];
5

6
   printf ("Starting %s...\n", bozo_exe);
7
   asprintf (&envs[ 0], "HOME=%s",  "/");
8
   asprintf (&envs[ 1], "PWD=%s",   "/");
9
   asprintf (&envs[ 2], "LANG=%s",  "C");
10
   asprintf (&envs[ 3], "USER=%s",  cf.uid);
11
   asprintf (&envs[ 4], "PATH=%s",  "/sbin:/usr/sbin:/bin:/usr/bin");
12
   asprintf (&envs[ 5], "SHELL=%s", "/bin/sh");
13
   envs[ 6] = NULL;
14

15
   args[ 0] = strdup (bozo_exe);
16
   args[ 1] = strdup ("-P");
17
// ...
18
   if (execve (args[0], args, envs)) {
19
// ...
20
   }
21
}

$ gcc --version

gcc (Debian 15.3.0-1) 15.3.0

: Bearbeitet durch User
#8072499
Lesenswert?

splint -strict nutzen

1
unbenannt.c:26:15: Possible out-of-bounds read: envs[5]
2
    Unable to resolve constraint:
3
    requires maxRead(envs @ unbenannt.c:26:15) >= 5
4
     needed to satisfy precondition:
5
    requires maxRead(envs @ unbenannt.c:26:15) >= 5
6
  A memory read references memory beyond the allocated storage. (Use
7
  -boundsread to inhibit warning)
8
unbenannt.c:28:4: Likely out-of-bounds store: envs[6]
9
    Unable to resolve constraint:
10
    requires 3 >= 6
11
     needed to satisfy precondition:
12
    requires maxSet(envs @ unbenannt.c:28:4) >= 6
13
  A memory write may write to an address beyond the allocated buffer. (Use
14
  -likelyboundswrite to inhibit warning)
(Firma: Starfleet) #8072507
Lesenswert?

IMHO ist es hier auch von der CPU/Speicer/System-architektur abhängig ob es zu Problemen kommt. Hier wird ein Feld aus vier char-pointern definiert, im weiteren verlauf aber mehr als die vier verwendet. (Oder liegt das Problem wo anders ?!)

Da kommt es IMHO darauf an wie der Compiler die pointer verteilt und wie die Adressberechnung in der CPU realisiert ist. Und vielleicht werden die überzählingen pointer resp. schreibzugriffe später wegoptimiert, da nie von diesen Adressen gelesen wird (dead code elimination).

Aber ich stimme zu, eine warning wäre angebracht. Vielleicht ist diese aber nicht eingeschaltet (-Wall) und manche warnings sind darüber hinaus an bestimmte Optimierungsstufen gebunden.

Würde ein exlizites malloc() helfen ?

"Enable optimization. Without at least -O2, GCC is not doing enough analysis to know what a is, and that you ran off the edge."

WTF? -.-

Das ist aber seit Jahrzehnten so, die eingestellte Optimierungsstufe bestimmt auch welchen Analysen der code unterworfen wird, die Idee ist wohl die compile-zeit möglichst kurz zu halten.

: Bearbeitet durch User
Persönliche Seite #8072537
Lesenswert?

Bradward B. schrieb:

IMHO ist es hier auch von der CPU/Speicer/System-architektur abhängig ob es zu Problemen kommt.

Einfach irgendwo auf den Stack zu schreiben dürfte auf den allermeisten Plattformen ziemliche Probleme erzeugen. Diese Überlegung ist aber müßig, es ist auf jeden Fall falsch und man kann sich absolut nicht darauf verlassen, dass da irgendwas bestimmtes passiert.

In diesem Fall könnte man durchaus erwarten, dass der Compiler das Problem erkennen würde weil die Definition der Variable und der Index direkt sichtbar sind. Aber auch das wäre nur eine Nettigkeit des Compilers, auf die man sich nicht verlassen sollte.

Zum debuggen solcher Problem kann man z.B. valgrind verwenden oder "-fsanitize=undefined" an den GCC übergeben.

(Firma: Starfleet) #8072540
Lesenswert?

IMHO ist es hier auch von der CPU/Speicer/System-architektur abhängig ob es zu Problemen kommt.

Einfach irgendwo auf den Stack zu schreiben dürfte auf den allermeisten Plattformen ziemliche Probleme erzeugen. Diese Überlegung ist aber müßig, es ist auf jeden Fall falsch und man kann sich absolut nicht darauf verlassen, dass da irgendwas bestimmtes passiert.

Heap oder stack - that is the question. Und dann wäre noch der cache.

Persönliche Seite #8072544
Lesenswert?

Bradward B. schrieb:

Heap oder stack - that is the question. Und dann wäre noch der cache.

Mir ist keine Plattform bekannt, die lokale Variablen auf einen Heap legen würde. Eine Plattform, die keinen Stack hat aber eine dynamische Speicherverwaltung ist schwer vorstellbar? Und selbst wenn, irgendwo auf den Heap zu schreiben ist auch nicht viel besser.

Der Cache ist irrelevant, für Single-Thread Anwendungen ist der Cache unsichtbar (DMA, mmap etc. ausgenommen).

: Bearbeitet durch User
#8072576
Lesenswert?

Rbx schrieb:

edit: Rust wäre natürlich auch noch eine Empfehlung.

Ich habs mir verkniffen. :-) Nein, ich will damit keinen C gegen Rust Krieg lostreten. Ich mag C.

Selbst mit Klimmzügen bringt man es nicht hin, so einen Rust-code zu kompilieren. Auch mit unsafe {...} wird es anstrengend und nur mit Vorsatz möglich.

Persönliche Seite #8072581
Lesenswert?

Nick schrieb:

Nein, ich will damit keinen C gegen Rust Krieg lostreten. Ich mag C.

Wobei das Beispiel hier stark nach der Art von Aufgabe aussieht, die man typischerweise mit einer gewöhnlichen Scriptsprache erledigen kann: Stringverarbeitung und Prozesse starten. Da ist die Performance vermutlich sowieso nicht besonders relevant. Die gezeigte Funktion ist in (Ba)sh ein Einzeiler, Python wäre für komplexere Prozeduren zu bevorzugen.

PS: Ist das strdup() nicht unnötig? chdir() statt "PWD=%s" würde mir auch etwas sauberer vorkommen.

(Firma: Starfleet) #8072615
Lesenswert?

In function 'foo', inlined from 'main' at <source>:30:5: <source>:19:8: warning: array subscript 6 is above array bounds of 'char *[4]' [-Warray-bounds=] 19 | envs[ 6] = NULL;

Wobei ja envs[4] und envs[5] auch noch eine Rüge verdienten. Zumal sie zuerst in Erscheinung treten.

Falls es um die "in order of appearance" dreht könnte aber

1
   char   *args[16], *envs[4];

mit Zeilennummer angemeckert werden - stünde da 'ne 7 wäre [4],[5] und [6] nicht unerwartet.

: Bearbeitet durch User
(Firma: Starfleet) #8072627
Lesenswert?

Norbert schrieb:

Bradward B. schrieb:

Falls es um die "in order of appearance" dreht

Nein, darum geht es nicht. Es wird nur Zeile 14 angemeckert (and rightfully so), jedoch nicht die Zeilen 13 und 15.

Und es nicht ausgeschlossen, das der "(Tipp-)Fehler" resp. die Korrektur in der Zeile steckt, die von der Warning nicht genannt wird, hier Zeile vier.

: Bearbeitet durch User
#8072630
Lesenswert?

Bradward B. schrieb:

Und es nicht ausgeschlossen, das der "(Tipp-)Fehler" resp. die Korrektur in der Zeile steckt, die von der Warning nicht genannt wird, hier Zeile vier.

Wie soll denn der Compiler wissen, was du dir gerade so wünscht? Der soll Fehler melden und nicht wie eine AI auf Ecstasy halluzinieren!

Im Übrigen gibt er diesbezüglich eine /note/ aus.

: Bearbeitet durch User
#8072645
Lesenswert?

Niklas G. schrieb:

Wobei das Beispiel hier stark nach der Art von Aufgabe aussieht, die man typischerweise mit einer gewöhnlichen Scriptsprache erledigen kann:

Ja, kann man und 100 andere Sprachen auch. Der TO hat nur mal ein C-Problem. Du kannst mir natürlich vorwerfen, dass ich Rust erwähnt habe. Zumindest sind C und Rust aus der gleichen Familie (darf man das sagen?). Aber nochmal, einen Programmiersprachen-Krieg wollte ich nicht anzetteln und will ich auch nicht weiterführen. Dafür kenn ich zu viele Sprachen.

Niklas G. schrieb:

PS: Ist das strdup() nicht unnötig?

Über das erste (bozo_exe) kann man diskuptieren, dann müsste es aber im Kommentar stehen, dass der Aufrufer dafür verantwortlich ist. Eine brandgefährliche Taktik! Beim zweiten: Wer garantiert mir, dass das als Konstante irgendwo steht? ein static const hätte das sichergestellt. Auf was anderes würde ich mich nicht einlassen wollen. Ja, OK, muss ja irgendwo stehen. Dann ist es zumindest für mich schlechter Programmierstil.

D. Knuth "Literate Programming", lesenswert.

Norbert schrieb:

s wird nur Zeile 14 angemeckert (and rightfully so), jedoch nicht die Zeilen 13 und 15.

Spasseshalber mal die angemeckerte Zeile auskommentieren und dann nochmal versuchen. Die Compiler kommen bei Mehrfachfehlern schon mal ins Trudeln.

#8072650
Lesenswert?

Bradward B. schrieb:

Und dann wäre noch der cache.

Niklas G. schrieb:

Der Cache ist irrelevant,

Da ist üblicherweise nur das Alignment wichtig.

Nick schrieb:

Nein, ich will damit keinen C gegen Rust Krieg lostreten.

Ist doch auch nicht nötig, wir sind doch hier nicht so eine Sekte wie auf Stack Overflow. (Stack Overflow is Dead (Here's Why) - https://www.youtube.com/watch?v=dsfqyVYkrbM)

: Bearbeitet durch User
#8072654
Lesenswert?

Mikro 7. schrieb:

Interessant...

https://stackoverflow.com/questions/382993/why-do-compilers-not-warn-about-out-of-bounds-static-array-indices

"Enable optimization. Without at least -O2, GCC is not doing enough analysis to know what a is, and that you ran off the edge."

Das wäre jetzt auch mein Vorschlag gewesen, mal die Optimierungen einzuschalten.

WTF? -.-

Wie meinst? Der Optimizer muss eine viel detaillierte Code-Analyse machen, um zu erkennen, welche Teile bereits zur Compilezeit durchgeführt werden können. Das führt natürlich auch dazu, dass dann solche Fehler leichter gefunden werden können, weil sie eben schon zur Compilezeit auftreten und nicht erst zur Laufzeit.

Persönliche Seite #8072658
Lesenswert?

Nick schrieb:

Beim zweiten: Wer garantiert mir, dass das als Konstante irgendwo steht?

Wozu wäre das nötig? Beim Aufruf von execve() kann der Kernel den Dateinamen und das "-P" problemlos lesen. execve() kehrt bei Erfolg nicht zurück, der Speicherbereich des Prozesses wird durch das neue Programm ersetzt und der Kernel kopiert die Strings in den neuen Prozess hinein. Es spielt also keine Rolle was das Programm sonst noch mit den Strings machen will, das wird schließlich nicht mehr ausgeführt.

Nur im Fehlerfall kehrt execve() zurück, aber sofern die Strings nicht in der Fehlerbehandlung irgendwo anders hin gereicht und dort verwendet werden ist das strdup() immer noch unnötig.

Rbx schrieb:

Niklas G. schrieb:

Der Cache ist irrelevant,

Da ist üblicherweise nur das Alignment wichtig.

Nur für die Geschwindigkeit, nicht die Korrektheit. Der Cache kann wohl immer exakt das gleiche Alignment was der Prozessor sonst auch kann. Ergo: Transparent.

(Firma: Starfleet) #8072665
Lesenswert?

Wie soll denn der Compiler wissen, was du dir gerade so wünscht? Der soll Fehler melden und nicht wie eine AI auf Ecstasy halluzinieren!

Der Compiler hat einen Widerspruch zwischen mehren Zeilen endteckt, den könnter er vollständig nennen. Der Widerspruch besteht hier in der Größe des feldes und seiner Nutzung. Also entweder ist das Feld zu klein oder der Zugriff zuviel. Genau das könnte der Compiler melden. Er könnte auch die Größe automatisch anpassen und das melden.

Vor Jahren gab es mal einen schweres Sicherheitsproblem, weil ein Programmiere einen Zugriff auf nichtinitialisierten Speicher wegen einer Warning diesbezüglich umschrieb. Blöd das der ursprüngliche Programmierer, den Speicher absichtlich uninitialisiert lies, um so eine Zufällige Größe für die Verschlüsselung zu generieren. Durch den vermeintlichen Fix durch zwangs-initialisierung war der Zufall natürlich dahin ... manchmal ist eben zuviel Determinismus schädlich.

Persönliche Seite #8072670
Lesenswert?

Bradward B. schrieb:

den Speicher absichtlich uninitialisiert lies, um so eine Zufällige Größe für die Verschlüsselung zu generieren

Wer sich auf den Inhalt von nicht-initialisiertem Speicher verlässt, auch als Zufalls-Generator, dem ist nicht zu helfen. Die Verteilung der Zufallsdaten kann sehr schlecht sein, z.B. "Immer Null", oder auch immer der gleiche Datenrest aus einer zuvor laufenden Prozedur. Jeder, der mit Kryptographie arbeitet sollte wissen wie man an gute Zufallsdaten kommt.

: Bearbeitet durch User
#8072672
Lesenswert?

Norbert schrieb:

Oliver S. schrieb:

In function 'foo', inlined from 'main' at <source>:30:5: <source>:19:8: warning: array subscript 6 is above array bounds of 'char *[4]' [-Warray-bounds=] 19 | envs[ 6] = NULL; | ~~~~^~~~

Mit Optimierung -O3

Oliver

Wobei ja envs[4] und envs[5] auch noch eine Rüge verdienten. Zumal sie zuerst in Erscheinung treten.

envs[5] meckert er auch an, envs[4] aber nicht.

Oliver

#8072678
Lesenswert?

Bradward B. schrieb:

Der Compiler hat einen Widerspruch zwischen mehren Zeilen endteckt, den könnter er vollständig nennen. Der Widerspruch besteht hier in der Größe des feldes und seiner Nutzung. Also entweder ist das Feld zu klein oder der Zugriff zuviel. Genau das könnte der Compiler melden.

Lies dir doch bitte mal die Fehlermeldung genau durch. Beitrag "Re: Fühlt sich der gcc hier veralbert?"

  • Die Zeile 7 ist syntaktisch absolut OK.

  • Die Zeilen 13, 14 und 15 sind erkennbar fehlerhaft (out of bounds)

  • Nur Zeile 14 wird gemeldet.

  • Und der Compiler generiert eine /note/ bzgl. Zeile 7

Im Grunde genommen hätte er noch nicht einmal eine Warnung, sondern vielmehr einen(drei) Fehler generieren müssen.

: Bearbeitet durch User
(Firma: Starfleet) #8072702
Lesenswert?

  • Die Zeile 7 ist syntaktisch absolut OK.

  • Die Zeilen 13, 14 und 15 sind erkennbar fehlerhaft (out of bounds)

  • Nur Zeile 14 wird gemeldet.

  • Und der Compiler generiert eine /note/ bzgl. Zeile 7

Im Grunde genommen hätte er noch nicht einmal eine Warnung, sondern vielmehr einen(drei) Fehler generieren müssen.

Und diese drei "Fehler" wären mit einer Anpassung der Zeile7 keine 'Fehler' mehr. Vom Syntax her sind alle Zeilen in Ordnung, sonst hätte schon der Parser gekotzt. "Programmiersprachlich" ist das Ganze auch in Ordnung, Speicherschutz ist in C nunmal in der Verantwortung des Coders und nicht in der des Compilers. Wenn es Probleme gibt, dann zur Laufzeit, und die "fängt" ohnehin nicht der Compiler ab, sonder die runtime.lib. Oder das Betriebssystem, bspw."segementation fault".

Und Deklaration ist nun mal m.E keine Allokation. IMHO ist es halt im Programmier-Alltag nicht ungewöhnlich, das man Tage nach der array-deklaration drauf kommt, das man doch etwas mehr benötigt, aber im "flow" vergisst, die Dekleration händisch anzupassen. Vielleicht verhindern ja " Programmiersprachen aus dem Bereich 'betreutes Programmieren' solche "menschlichen Fehler". Ich meine bspw. ADA bringt extra range checking mit (auch wenn es aus Performancegründen gerne deaktiviert wird).

: Bearbeitet durch User
Persönliche Seite #8072709
Lesenswert?

Bradward B. schrieb:

"Programmiersprachlich" ist das Ganze auch in Ordnung,

Nein. Der C-Standard definiert so etwas ganz eindeutig als fehlerhaften Code. Es ist kein korrektes C-Programm. Nicht alle Fehler sind Syntaxfehler, und C ist mehr als nur Syntax. Nur weil ein Fehler nicht vom Compiler gefunden wird, heißt das noch lange nicht dass es korrekt ist.

"Nachts ist es kälter als draußen" ist zwar sprachlich auch korrekt, aber inhaltlich sinnlos.

: Bearbeitet durch User
#8072713
Lesenswert?

Rolf M. schrieb:

WTF? -.-

Wie meinst? Der Optimizer muss eine viel detaillierte Code-Analyse machen, um zu erkennen, welche Teile bereits zur Compilezeit durchgeführt werden können. Das führt natürlich auch dazu, dass dann solche Fehler leichter gefunden werden können, weil sie eben schon zur Compilezeit auftreten und nicht erst zur Laufzeit.

Das mag ja so sein. Ich finde es aber sehr irritierend.

OP #8072714
Lesenswert?

Vielen Dank miteinander! Das ist doch interessanter geworden als ich dachte. Also gut, selbst solche offensichtlichen Fehler werden erst ab -O2 gefunden. Ist mir recht, hier auf dem PC kann ich Assembler sowieso nicht lesen. Aus Gewohnheit benutze ich immer nur -O, als Kompromiss zwecks Debugging bei den Cortex-M.

Norbert schrieb:

Im Grunde genommen hätte er noch nicht einmal eine Warnung, sondern vielmehr einen(drei) Fehler generieren müssen.

Das wäre nett, aber er hat das so falsch, praktisch wörtlich, übersetzt. Wohl, weil envs[5] und envs[6] ziemlich sicher UB ist? envs[4] ist evt. irgendwie ok, wegen der Sonderregel "just beyond an array".

Niklas G. schrieb:

PS: Ist das strdup() nicht unnötig? chdir() statt "PWD=%s" würde mir auch etwas sauberer vorkommen.

chdir() wird auch gemacht. Ich denke, im Zweifelsfall braucht man beides.

strdup() ist eine ganz andere, besonders traurige Geschichte. Sowohl args[1]="-P"; als auch args[1]=(char *)"-P" werden angemeckert. Sehr unpraktisch, aber eigentlich richtig, "-P" ist ja const und die Pointer nicht.

#8072715
Lesenswert?

Norbert schrieb:

Oliver S. schrieb:

auf godbolt liefern gcc 12 - 16, jeweils letzte Version, alle die gleichen Meldungen, mit -Wall -Wextra -O3

Und ein gcc version 10.2.1 20210110 (Debian 10.2.1-6) macht's eben nicht!

Wie man hier erkennen kann: Beitrag "Re: Fühlt sich der gcc hier veralbert?"

Gerade noch die anderen probiert. Alle auf godbolt vorhandenen gccs, also 4-16, bringen die Warnungen.

clang22 meckert genauso auch nur 5 und 6 an.

Oliver

(Firma: Starfleet) #8072722
Lesenswert?

Nein. Der C-Standard definiert so etwas ganz eindeutig als fehlerhaften Code.

Nope, vielleicht als "undefined behaviour" aber nicht als "Failure" was den Compile-run abbrechen liesse.

Aber gern kann man die entsprechende Passage im Standard zeigen, wo dergleichen als syntax failure definiert ist.

: Bearbeitet durch User
Persönliche Seite #8072724
Lesenswert?

Bauform B. schrieb:

strdup() ist eine ganz andere, besonders traurige Geschichte. Sowohl args[1]="-P"; als auch args[1]=(char *)"-P" werden angemeckert.

Das eigentliche Problem ist dass die Argumente von execve() so deklariert sind dass argv und envp Arrays von Zeigern auf nicht-Konstante Strings sein müssen. Das hat aber historische Gründe, tatsächlich wird execve() diese Strings nicht verändern. Daher kann man das "const" legal wegcasten und spart sich das strdup():

1
const char * args [] = { bozo_exe, "-P", NULL };
2
...
3
if (execve (args[0], (char* const *) args, envs)) {

Bradward B. schrieb:

Nope, vielleicht als "undefined behaviour" aber nicht als "Failure" was den Compile-run abbrechen liesse.

Programme mit undefined behaviour sind aber fehlerhaft und müssen noch nichtmal irgendwie sinnvoll übersetzt werden.

Bradward B. schrieb:

Aber gern kann man die entsprechende Passage im Standard zeigen, wo dergleichen als syntax failure definiert ist.

Ich habe nicht behauptet dass es Syntaxfehler sind, im Gegenteil. Aber Fehler sind es dennoch. Denn C definiert, wie gesagt, mehr als nur die Syntax, und es definiert solche Out-Of-Bounds Zugriffe als Fehler.

#8072725
Lesenswert?

Oliver S. schrieb:

Gerade noch die anderen probiert. Alle auf godbolt vorhandenen gccs, also 4-16, bringen die Warnungen.

Ja dann mach's halt richtig.

1
$ gcc -v |& grep "gcc version"
2
gcc version 10.2.1 20210110 (Debian 10.2.1-6) 
3

4
$ gcc -Wall -Wextra -pedantic -std=c2x -O3 -save-temps -c unbenannt.c
5
unbenannt.c: In function ‘main’:
6
unbenannt.c:14:5: warning: array subscript 5 is above array bounds of ‘char *[4]’ [-Warray-bounds]
7
   14 |     asprintf (&envs[5], "SHELL=%s", "/bin/sh");
8
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9
unbenannt.c:7:11: note: while referencing ‘envs’
10
    7 |     char* envs[4];
11
      |           ^~~~
12

13
$ vi unbenannt.c  # Zeile 14 auskommentiert.
14

15
$ gcc -Wall -Wextra -pedantic -std=c2x -O3 -save-temps -c unbenannt.c
16
~/tmp-share$

Kein Fehler mehr. GAR KEINER! Und das trotz 13 und 15.

1
  1 #define _GNU_SOURCE
2
  2 #include <stdio.h>
3
  3 #include <string.h>
4
  4 
5
  5 static void foo (const char *bozo_exe) {
6
  6     char* args[16];
7
  7     char* envs[4];
8
  8     printf ("Starting %s...\n", bozo_exe);
9
  9     asprintf (&envs[0], "HOME=%s",  "/");
10
 10     asprintf (&envs[1], "PWD=%s",   "/");
11
 11     asprintf (&envs[2], "LANG=%s",  "C");
12
 12     asprintf (&envs[3], "USER=%s",  "cf.uid");
13
 13     asprintf (&envs[4], "PATH=%s",  "/sbin:/usr/sbin:/bin:/usr/bin");
14
 14     // asprintf (&envs[5], "SHELL=%s", "/bin/sh");
15
 15     envs[6] = NULL;
16
 16     args[0] = strdup (bozo_exe);
17
 17     args[1] = strdup ("-P");
18
 18     (void)args;
19
 19 }
20
 20 
21
 21 int main(void) {
22
 22     const char *abc = "ABC";
23
 23     foo(abc);
24
 24     return 0;
25
 25 }

Vielleicht iss ja jetzt langsam mal gut.

#8072731
Lesenswert?

Norbert schrieb:

Vielleicht iss ja jetzt langsam mal gut.

Nun ja, du hast mit dem Geplärre angefangen…

Ernsthafte Programmierer compilieren mit adress sanitizer, der dann bei der Ausführung auch mit einem steinalten gcc 10 sagt:

================================================================= ==1==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffef8a45dd0 at pc …

In diesem Sinne.

Oliver

(Firma: Starfleet) #8072733
Lesenswert?

Aber Fehler sind es dennoch. Denn C definiert, wie gesagt, mehr als nur die Syntax, und es definiert solche Out-Of-Bounds Zugriffe als Fehler.

Literaturstelle, bitte?

und ich glaube hier wird munter an den Begriffen vorbei geredet. "Error", "Warning" ist doch hier im Sinne von Compiler-Meldungen gemeint. Und solange der Compiler einen assembler-code für den Zugriff auf diese Address generieren kann, ist programmiertechnisch alles akzeptabel, auch wenn es während der runtime "krachen" sollte.

"Errors report problems that make it impossible to compile your program" aus: https://gcc.gnu.org/onlinedocs/gcc-11.5.0/gcc/Warnings-and-Errors.html

Beispiel Routenführung wie:

  • " gehe zum Haus "Kirchgasse", Nr. 12
  • " von dort gehe drei Grundstücke nach rechts weiter "

völlig korrekt auch falls in dem jeweiligen Ort die Bebauung der Kirchgasse mit der Nummer 12 enden sollte. Der "Fehler" steckt da nicht im Programm sondern in der tatsächlichen Bebauung.

: Bearbeitet durch User
Persönliche Seite #8072745
Lesenswert?

Bradward B. schrieb:

Literaturstelle, bitte?

C-Standard Draft n3220:

3.5.3 1 undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this document imposes no requirements

2 Note 1 to entry: Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message)

6.5.4.2 Address and indirection operators If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

Bradward B. schrieb:

"Error", "Warning" ist doch hier im Sinne von Compiler-Meldungen gemeint.

Für dich vielleicht. Aber es gibt wie gesagt auch andere Fehler.

Bradward B. schrieb:

Und solange der Compiler einen assembler-code für den Zugriff auf diese Address generieren kann, ist programmiertechnisch alles akzeptabel, auch wenn es während der runtime "krachen" sollte.

Was ist "programmiertechnisch akzeptabel"? Wenn der C-Standard etwas als "undefined behaviour" und daher als "erroneous" deklariert, kann man kein sinnvolles oder vorhersehbares Ergebnis erwarten. Es kann zwar sein dass manche Compiler ausnahmsweise bestimmte Fälle dieser Art doch wohldefiniert umsetzen, aber sich darauf zu verlassen ist gefährlich. Mir ist kein C-Compiler bekannt, der für Array-Out-Of-Bounds Zugriffe ein konsistentes Verhalten definiert, nicht einmal einen Absturz.

Ein Quellcode, der kein konsistentes sinnvolles Verhalten liefern kann und für den in der Sprachspezifikation und in der Compiler-Dokumentation kein Verhalten gegeben ist, mag für dich vielleicht "programmiertechnisch akzeptabel" sein, aber selbst der C-Standard nennt dies "erroneous" und für mich und den Rest der Welt ist das ein Fehler, auch wenn der Compiler dafür einen (sinnlosen) Assemblercode generieren kann. Was korrekt ist und was nicht wird nur vom C-Standard vorgegeben, und nicht ob irgendein Compiler dafür irgendeinen Assemblercode für ausspucken kann.

Wenn im Code eine nicht-existente Funktion aufgerufen wird, was der Linker mit "undefined reference" quittiert, ist das für dich "programmiertechnisch akzeptabel" weil der Compiler keine Fehlermeldung ausgibt? Was ist mit Division durch Null, oder Dereferenzierung von Null-Pointern?

Der GCC könnte für völlig vermurksten Code auch zufällige Assembler-Instruktionen rauswerfen, dadurch wird der Code noch lange nicht korrekt.

C ist eben mehr als nur der Compiler und die Übersetzung in Assemblercode. C definiert auch ein bestimmtes Laufzeitverhalten. Ein korrektes C-Programm liefert immer das gleiche, vorhersehbare Ergebnis. Also genau das, was man von einem Computer erwartet. Würde C nur eine Syntax vorgeben die in Assembler übersetzt wird, ohne ein Laufzeitverhalten vorzugeben, wäre die Sprache ziemlich nutzlos - was will man mit einer Sprache, bei der "2+2" immer mal etwas unterschiedliches ausgibt? Korrekte, und nur korrekte, Programme liefern konsistente Ergebnisse. Ein fehlerhaftes Programm liefert irgendwas, das ist nutzlos und wird somit allgemein als Fehler angesehen, und nicht als "programmiertechnisch akzeptabel".

#8072747
Lesenswert?

Oliver S. schrieb:

Gerade noch die anderen probiert. Alle auf godbolt vorhandenen gccs, also 4-16, bringen die Warnungen.

Und wieder einmal zeigt sich die Gültigkeit des Sprichwortes:

Lügen haben kurze Beine!

Ich sehe da – wie die ganze Zeit von mir behauptet – nur EINE Warnung und nicht DREI!

Angehängte Dateien:
: Bearbeitet durch User
#8072749
Lesenswert?

Niklas G. schrieb:

Wenn der C-Standard etwas als "undefined behaviour" und daher als "erroneous" deklariert, kann man kein sinnvolles oder vorhersehbares Ergebnis erwarten.

Der STandard definiert den Begriff. Nich was erroneous ist. Da hätte er viel zu tun um alle möglichen Fehler abzudecken.

Wie ein Kollege zu sagen pflegte, wenn das Programm erstmalig compiliert wurde "Formal ist es schon mal richtig"

Persönliche Seite #8072753
Lesenswert?

Nick schrieb:

Nich was erroneous ist. Da hätte er viel zu tun um alle möglichen Fehler abzudecken.

Was undefiniert ist, ist erroneous. Manche Sachen sind explizit als undefiniert angegeben. Alles was nicht definiert ist, ist logischerweise undefined.

Nick schrieb:

Wie ein Kollege zu sagen pflegte, wenn das Programm erstmalig compiliert wurde "Formal ist es schon mal richtig"

Stimmt halt nicht. Höchstens syntaktisch korrekt. Der C-Standard definiert formal die Korrektheit nicht nur auf Basis der Syntax sondern auch "inhaltlich". Diese "inhaltlichen" Definitionen sind durchaus komplex, d.h. das Zusammenspiel mehrerer einzelner Elemente (z.B. Array-Definition und Array-Zugriff) können fehlerhaft sein, auch wenn die einzelnen Teile nicht unbedingt fehlerhaft sein müssen. Und genau deswegen kann der Compiler solche Probleme nicht im Allgemeinen finden.

Ist ein Programm, das laut C-Standard "erroneous" ist, aber von einem Compiler in Assemblercode übersetzt wird, von einem anderen Compiler (oder sogar dem selben Compiler mit anderen Optionen oder in anderer Version) aber mit Fehlermeldung quittiert wird, "korrekt"?

: Bearbeitet durch User
(Firma: Starfleet) #8072757
Lesenswert?

Nick schrieb:

Niklas G. schrieb:

Wenn der C-Standard etwas als "undefined behaviour" und daher als "erroneous" deklariert, kann man kein sinnvolles oder vorhersehbares Ergebnis erwarten.

Doch kann man, " ... (or) to behaving during translation or program execution in a documented manner characteristic of the environment (or) ... " Also das kann funktionieren, muss aber nicht, das kann komplett wegfallen oder es fehlerhaft mit ungewollten Ergebnissen durchlaufen. Eben "undefined", russisch Roulette auf der black box.

Der STandard definiert den Begriff. Nich was erroneous ist. Da hätte er viel zu tun um alle möglichen Fehler abzudecken.

Wie ein Kollege zu sagen pflegte, wenn das Programm erstmalig compiliert wurde "Formal ist es schon mal richtig"

Eben. Und danke für den Auszug, das ist mal 'ne sachliche Grundlage. Vielleicht beziehe ich mich auf eine ältere Definition von C aber Zugriffe auf nicht deklarierte Feldelemente sind in C an sich nicht verboten. Es wird davon abgeraten, weil das Verhalten je nach Maschine unterschiedlich sein kann. Vielleicht hat man ja Gründe für "dirty tricks", C will ja dem Programmierer nicht die Kontrolle über die Maschine streitig machen (das wollen die Programmiersprachen aus dem Bereich "betreutes Programmieren") sondern erleichtern. Vielleicht will man ja auch das memory managment der Maschine testen, wie es sich bei solchen Zugriffen verhält - kackt das System ab oder reagiert es "robust" auf solche "Speicherschreiber".

Deshalb gibt es ja auch Ergänzungen wie MISRA die aus dem Hackmesser "C" ein kindersicheres Werkzeug machen wollen, also dem Löwen die Zähne ziehen um ihn zum "Plüschtier" zu entschärfen.

"Error", "Warning" ist doch hier im Sinne von Compiler-Meldungen gemeint.

Für dich vielleicht. Aber es gibt wie gesagt auch andere Fehler. "erroneous"

Das thread-thema lautet "gcc veralbert", das wird hier durch die Bank auf den Compiler bezogen, nicht auf C an sich.

: Bearbeitet durch User
Persönliche Seite #8072770
Lesenswert?

Bradward B. schrieb:

Also das kann funktionieren

Aber nur, wenn die Plattform (inkl. Compiler) das entsprechend spezifiziert. Aber bei Array-Out-Of-Bound ist das bei keinem Compiler der Fall.

Bradward B. schrieb:

Zugriffe auf nicht deklarierte Feldelemente sind in C an sich nicht verboten

Was ist für dich "verboten"?

Es ist "undefined behaviour". Noch verbotener wäre nur wenn der C-Standard vom Compiler verlangen würde, immer sofort mit Fehlermeldung zu quittieren - da das aber nicht möglich ist (Satz von Rice etc), kann der Standard es nur als UB deklarieren.

Bradward B. schrieb:

Es wird davon abgeraten, weil das Verhalten je nach Maschine unterschiedlich sein kann.

Von welchen Undefined Behaviour Dingen würdest du denn nicht abraten?

Bradward B. schrieb:

C will ja dem Programmierer nicht die Kontrolle über die Maschine streitig machen

C abstrahiert die Programmierung von der konkreten Plattform, was notwendigerweise mit einem Kontrollverlust einhergeht. Gerade in Bezug auf Speicherzugriffe sind manche Dinge in C schlicht nicht möglich.

Bradward B. schrieb:

Vielleicht will man ja auch das memory managment der Maschine testen

Dafür ist C nicht gemacht. Wie gesagt kann der Compiler solche wilden Speicherzugriffe auch einfach aus dem Code verschwinden lassen ohne den C-Standard zu verletzen - damit ist C nicht dazu geeignet um irgendwo im Speicher herumzuporkeln.

Bradward B. schrieb:

Programmiersprachen aus dem Bereich "betreutes Programmieren"

Die können letztendlich genau das gleiche wie C, da all diese Sprachen turing-vollständig sind. C kann vielleicht manche Sachen schneller, was aber damit erkauft wird, dass viele Fehler nicht so leicht ersichtlich sind. Paradoxerweise ist z.B. FORTRAN teilweise noch schneller als C, gerade weil es manche Schweinereien verbietet.

Bradward B. schrieb:

Das thread-thema lautet "gcc veralbert", das wird hier durch die Bank auf den Compiler bezogen, nicht auf C an sich.

Selbst der GCC meckert den Arrayzugriff je nach Optionen an, kann also nicht so besonders korrekt sein.

(Firma: Starfleet) #8072772
Lesenswert?

Dafür ist C nicht gemacht. Wie gesagt kann der Compiler solche wilden Speicherzugriffe auch einfach aus dem Code verschwinden lassen ohne den C-Standard zu verletzen - damit ist C nicht dazu geeignet um irgendwo im Speicher herumzuporkeln.

Schön wärs, aber C kennt IMHO keinerlei Beschränkung bezüglich Speicherzugriffe.

Zwar lässt C gern mal MEMORY-mapped IO Zugriffe gerne mal bei gut gemeinter aber schlecht gemachter Optimierung aus, aber nur so lange bis man das keyword "volatile" gelernt hat. Ist gerade bei µC-Programmierung somit das wichtigste keyword.

Persönliche Seite #8072778
Lesenswert?

Bradward B. schrieb:

Schön wärs, aber C kennt IMHO keinerlei Beschränkung bezüglich Speicherzugriffe.

Dann kompilier mal mit "-fsanitize=undefined" . Nur weil C das nicht immer verhindert, heißt das nicht, dass C da besonders für geeignet wäre. Nur weil man mit einem Golf schon gegen einen Brückenpfeiler fahren kann, ist der Golf nicht dafür konzipiert oder besonders effektiv dabei, Brücken abzureißen. Empfehlen würde ich das auch nicht.

Bradward B. schrieb:

Zwar lässt C gern mal MEMORY-mapped IO Zugriffe gerne mal bei gut gemeinter aber schlecht gemachter Optimierung aus, aber nur so lange bis man das keyword "volatile" gelernt hat.

volatile ist aber ein grober Hammer, mit dem man auch nicht alles erreichen kann.

Bradward B. schrieb:

Ist gerade bei µC-Programmierung somit das wichtigste keyword.

Komisch, manche Leute schaffen es komplexe Mikrocontroller-Anwendungen in C oder C++ ganz ohne volatile zu schreiben...

(Firma: Starfleet) #8072782
Lesenswert?

Niklas G. schrieb:

Bradward B. schrieb:

Schön wärs, aber C kennt IMHO keinerlei Beschränkung bezüglich Speicherzugriffe.

Dann kompilier mal mit "-fsanitize=undefined" . Nur weil C das nicht immer verhindert, heißt das nicht, dass C da besonders für geeignet wäre.

Doch C ist schon ziemlich hardwarenah, das wurde ja extra für hardwarenahe Sachen gemacht. Und die Option sanitize ist ne ziemlich neumodische Sonderlocke, wurde meines Wissens 2013 eingeführt.

Nur weil man mit einem Golf schon gegen einen Brückenpfeiler fahren kann, ist der Golf nicht dafür konzipiert oder besonders effektiv dabei, Brücken abzureißen.

C würde ich jetzt nicht mit ner Studentschleuder wie den Golf vergleichen, auch wenn man beides öfters an Hochschulen sieht. C ist unter den Programmiersprachen schon so "geländegängig" wie ein Unimog. und nicht jeder Student ist ein Warmduscher oder ein "real programmer" ;-) https://thehardcorecoder.com/2014/03/07/real-programmers/

Komisch, manche Leute schaffen es komplexe Mikrocontroller-Anwendungen in C oder C++ ganz ohne volatile zu schreiben...

Klar, wenn man die Optimierung disabled, oder nicht direkt auf die IO-Ports zugreift. C ist halt Assembler mit ein bißchen zuckerguß drüber.

Persönliche Seite #8072785
Lesenswert?

Bradward B. schrieb:

Doch C ist schon ziemlich hardwarenah, das wurde ja extra für hardwarenahe Sachen gemacht.

Es wurde nicht dafür gemacht, um mit sinnvollen Ergebnissen irgendwo in den Speicher schreiben zu können.

Bradward B. schrieb:

Und die Option sanitize ist ne ziemlich neumodische Sonderlocke, wurde meines Wissens 2013 eingeführt.

Durch die Einführung von einzelnen Features in einzelnen Compilern ändert sich aber nicht die Bedeutung des Standards. Vielmehr waren wilde Zugriffe schon immer falsch, der GCC kann sie jetzt nur besser aufspüren.

Bradward B. schrieb:

C ist unter den Programmiersprachen schon so "geländegängig" wie ein Unimog.

Altmodisch und ineffizient?

Bradward B. schrieb:

Klar, wenn man die Optimierung disabled, oder nicht direkt auf die IO-Ports zugreift.

Sorry, ich hab was durcheinander geworfen, volatile ist nicht nötig für die Synchronisierung von Variablenzugriffen zwischen main() und ISR's (auch mit Optimierungen), für memory-mapped Register gibt es zwar auch andere Möglichkeiten aber die sind nicht wirklich praktikabel.

Dennoch ist volatile eine Krücke, eigentlich bräuchte C hier ein präziseres Werkzeug, aber weil C eben nicht für direktes Rumbasteln am Speicher gemacht ist wird's das wohl nicht geben. Tatsächlich gibt es sogar Pläne, "volatile" ganz zu entfernen...

Bradward B. schrieb:

C ist halt Assembler mit ein bißchen zuckerguß drüber.

Das ist übervereinfacht.

: Bearbeitet durch User
#8072798
Lesenswert?

Oliver S. schrieb:

Mag sein. Ich sehe drei. Und nun?

Da man davon ausgehen darf, dass es nicht mehrere unterschiedlich arbeitende Godbolt Seiten gibt…

UND da ich bereits mehrere Screenshots von nur EINEM Fehler bzw. KEINEM Fehler zur Verfügung gestellt habe…

UND da alle Anderen diese Situation ebenfalls problemlos nachvollziehen können, wäre es nun an der Zeit über eine schwerwiegende neuronal bedingte Komponente nachzudenken.

Denn etwas dreifach zu sehen was für alle nachvollziehbar nur einfach da ist, sollte man nicht auf die leichte Schulter nehmen.

#8072836
Lesenswert?

Norbert schrieb:

splint -strict nutzen

Okay, ein Linter ist natürlich immer eine feine Sache, um ein Problem so früh wie möglich zu entdecken. Hier bei uns werden zusätzlich dazu noch die Unit-, Integrations- und Systemtests vor Releases mit den Werkzeugen der Valgrind-Suite überprüft. Wir hatten schon mehrere Fälle, in denen damit Fehler gefunden wurden, die den Lintern zuvor entgangen waren.

Persönliche Seite #8072844
Lesenswert?

Nick schrieb:

dann kann ich auch gefahrlos über das Array hinaus schreiben/lesen wenn das z.B. char ist und ich innerhalb der Grenzen bleib.

Welche Architektur allokiert alles an Word-Grenzen? Die gängigen 32&64 bitter nicht, die würden dort durchaus andere einzelne char-Variablen ablegen. Was wenn der Compiler dann doch sieht dass du über die Grenze hinweg zugreifst und den Zugriff ganz wegoptimiert weil er davon ausgehen darf, dass UB nie auftritt? Was wenn der Compiler ein Canary-Byte dort ablegt?

(Firma: Starfleet) #8072866
Lesenswert?

Doch C ist schon ziemlich hardwarenah, das wurde ja extra für hardwarenahe Sachen gemacht.

Es wurde nicht dafür gemacht, um mit sinnvollen Ergebnissen irgendwo in den Speicher schreiben zu können.

"Sinn" ist keine Eigenschaft eines Codes, Determiniertheit ist eine. Und ja (Arbeits-)Speicherzugriffe sind determiniert, man liest was darin geschrieben wurde. Ob der Speicher genüg Zellen hat, interessiert den Compiler nicht, das muss der C-Programmierer selber checken resp. der User sicherstellen.

Und die Option sanitize ist ne ziemlich neumodische Sonderlocke, wurde meines Wissens 2013 eingeführt.

Durch die Einführung von einzelnen Features in einzelnen Compilern ändert sich aber nicht die Bedeutung des Standards. Vielmehr waren wilde Zugriffe schon immer falsch, der GCC kann sie jetzt nur besser aufspüren.

Was sind "wilde Speicher-Zugriffe" ? Das definiert jeder anders. "Falsch/Richtig" ist auch so ein eigentlich fachfremder Begriff. "Division durch Null" ist nicht "falsch" sondern "Undefiniert". Und nochmals "Wilde Speicherzugriffe" mögen in den meisten Scenarion unerwünscht sein, sie sind aber möglich. Diverse Sicherheitslücken basieren auf "wilde Zugriffe" (bspw "RAW-Hammer"). Der Gedanke, ein System zu härten, indem man "wilde Zugriffe" und ähnlichen "Ungemach§ einfach verbietet, ist zwar naheliegend aber unrealistisch.

BTW: "Raw Hammer" beruht auf der indirekten Beeinflußung benachtbarter (wenige Namometer) Speicherzellen. Man "hämmert" solange auf eine Speicherzelle mit Zugriffen (Lesen würde genügen) ein bis die Ladung der Nachbarn (auf die man keinen Zugriff hat) sich verändert. https://www.heise.de/hintergrund/Der-Hammer-mit-dem-Rowhammer-2764420.html

: Bearbeitet durch User
#8072879
Lesenswert?

Norbert schrieb:

Da man davon ausgehen darf, dass es nicht mehrere unterschiedlich arbeitende Godbolt Seiten gibt…

UND da ich bereits mehrere Screenshots von nur EINEM Fehler bzw. KEINEM Fehler zur Verfügung gestellt habe…

Die deinen Horizont begrenzende Untertasse scheint etwas klein zu sein. Bei mir kommen, wie ich schon mehrfach schrieb, bei gcc4-gcc 16 immer Warnungen zu Index 5 und Index 6.

Oliver

Angehängte Dateien:
: Bearbeitet durch User
#8072890
Lesenswert?

Niklas G. schrieb:

Welche Architektur allokiert alles an Word-Grenzen? Die gängigen 32&64 bitter nicht, die würden dort durchaus andere einzelne char-Variablen ablegen.

Da muss ich die Frage umkehren: Wer macht das, dass nicht an Wort-Grenzen allokiert wird? Ich seh schon, woraus du hinaus willst. Wenn 1 char allokiert wird, dann braucht das bei 32 Bittern üblicherweise 8 Bytes. 4 Bytes für den pointer auf den nächsten alloc-block und 4 bytes für den einen char. Allokiert man 2 chars in einem array, bleibt es bei den 8 bytes. 4 char array immer noch 8 bytes, 5 char array dann 12 bytes. Gilt bei 32 bit Adressraum! Wenn der Adressraum über der Wortgrenze ist, dann ist der pointer auf den nächsten Block natürlich größer. Dass ich keine andere Methode der allokation kenne bedeutet nicht, dass andere Plattformen (>= 16 bit) es anders machen und ganz bewusst mis-aligned arbeiten um dadurch langsamer zu werden.

Niklas G. schrieb:

Was wenn der Compiler dann doch sieht dass du über die Grenze hinweg zugreifst und den Zugriff ganz wegoptimiert weil er davon ausgehen darf, dass UB nie auftritt?

Langsam! Es ist nicht die Aufgabe eines C-Compilers, den Unsinn des Programmierers abzufangen. Es muss lediglich garantieren, dass richtiger code auch richtig funktioniert. Wenn absichtlich/unabsichtlich Unsinn gemacht wird, kann das dem Compiler egal sein. Dass Compiler solche zusätzlichen Analysen machen, steht nicht im C-Standard. Das ist nur eine freundliche Zugabe. lint, statische/dynamische code-Analyse, bounds-checking, ... steht alles nicht im Standard. C ist keine "betreute Progammiersprache" (ich find die Bezeichnung witzig). Es ist und bleibt ein "Hochsprachen-Assembler" der extrem gefährlich ist. Das ist einfach der Preis, den man für Effektivität und Geschwindigkeit zahlt. Das ist auch die Motivation für Rust gewesen (mMn). Und auch, schon ewig her, für Obj-C, das die alloc-Geschichte anders handhabt (wobei ich das inzwischen vergessen hab wie genau das war). Wie sich das borrow-Konzept und die (genialen) slices in Rust auf die Performance und code-Größe auswirken weiß ich schlicht nicht. Letztendlich ist das auch für C egal. Es garantiert, dass richtiger code richtig funktioniert. Falscher code ist in der Verantwortung des Missbrauchenden.

#8072925
Lesenswert?

Nemopuk schrieb:

Allokiert man 2 chars in einem array, bleibt es bei den 8 bytes.

Nein

Dann ist es also so, dass ein char pointer in ein Array nach einem increment 4 Bytes weiter zeigt. Oder ein pChar + sizeof(char) äquivalent zu pChar + 4 ist. Aber sizeof(char) trotzdem 1 ist. Und folglich muss unterschieden werden, ob der ponter in ein statisches oder in ein allokiertes array zeigt.

Oder irgendwie so.

#8072931
Lesenswert?

Alexander S. schrieb:

Der Typ hat eine Abneigung gegenüber C (schreibt er selbst) und er hat nicht verstanden und will nicht verstehen warum C so ist wie es ist.

Ojojojoj! Die Einführung hat mir schon genügt. Die ist wirklich lesenswert, notfalls bitte nachholen. Fortran konnte ich ein bissl, hat mich immer an naturwissenschaftliches Basic erinnert. Ja, Fortran ist in manchen Bereichen wirklich mächtig (gewesen). Pascal mochte ich sehr, das war nach Basic meine erste sinnvolle Programmiersprache. Ada ist OK. Wer labern will, nimmt Cobol.

Das sind Sprachkonzepte die teilweise leider, teilweise erfreulicherweise untergegangen sind. Basic, Modula2, Eiffel, Cobol, Objective-C, PL-sql (würg).

Um das mal wieder zu bemerken: Ich hatte eine massive Abneigung gegenüber C.

#8072944
Lesenswert?

Norbert schrieb:

Dann lebst du in einem Paralleluniversum.

Natürlich. In einem, in dem es Gründe für so etwas gibt, und man, wenn man möchte, diese auch suchen und finden kann, anstatt kindergartenmässing rumzublöken.

Schau dir deinen Code an, schau dir den Originalcode oben an, und finde die Unterschiede. Das Thema hier ist nicht irgend ein Code, sondern der des TO. Und dann kannst du darüber nachdenken, was z.B. der Optimierer alles mit Variablen anstellt, die zwar geschrieben, aber nie benutzt werden. Oder was es noch für Gründe geben könnte.

In diesem Sinne

Oliver

#8073020
Lesenswert?

Nick schrieb:

Dann ist es also so, dass ein char pointer in ein Array nach einem increment 4 Bytes weiter zeigt.

Nein auch das nicht.

Ein char ist 1 Byte groß, egal welches Alignment der Compiler anwendet. Wenn du einen Zeiger auf ein char inkrementierst, dann wird er um 1 erhöht. Und das passt auch gut zum char-array, denn innerhalb des Array liegen die Zeichen Byte für Byte direkt hintereinander (ohne Lücken).

1
#include <stdio.h>
2

3
int main() {
4
  char a ='A';
5
  char b ='B';
6
  char c ='C';
7
  printf("a='%c', b='%c, c='%c'\n",a,b,c);
8
  printf("addr von a='%0x', addr von b='%0x', addr von c='%0x'\n",&a,&b,&c);
9

10
  char* p1=&a; 
11
  char* p2=p1;
12
  p2++;
13
  printf("p1=%0x, p2=%0x\n",p1,p2);
14
  
15
  char d[] ="Hallo";
16
  printf("addr von H='%0x', addr von a='%0x', addr von l='%0x', addr von l='%0x', addr von o='%0x'\n",&d[0],&d[1],&d[2],&d[3],&d[4]);
17
}

Unter Linux ausgeführt kommt dies heraus:

1
$ gcc test.c && ./a.out
2

3
a='A', b='B, c='C'
4
addr von a='774fb05f', addr von b='774fb05e', addr von c='774fb05d'
5
p1=774fb05f, p2=774fb060
6
addr von H='774fb057', addr von a='774fb058', addr von l='774fb059', addr von l='774fb05a', addr von o='774fb05b'

Wie du siehst hat der Compiler die char Variablen ohne Lücke direkt hintereinander gelegt (in rückwärts-Reihenfolge, warum auch immer).

Der Zeiger wurde um 1 inkrementiert.

Die Zeichen vom Array liegen direkt aufeinander folgend im Speicher.

Das war übrigens eine 64 Bit Maschine. Zum Vergleich mit erzwungenem Alignment 8 (64 Bit):

1
int main() {
2
  char a __attribute__ ((aligned (8))) ='A';
3
  char b __attribute__ ((aligned (8))) ='B';
4
  char c __attribute__ ((aligned (8))) ='C';
5
  printf("a='%c', b='%c, c='%c'\n",a,b,c);
6
  printf("addr von a='%0x', addr von b='%0x', addr von c='%0x'\n",&a,&b,&c);
7

8
  char* p1=&a; 
9
  char* p2=p1;
10
  p2++;
11
  printf("p1=%0x, p2=%0x\n",p1,p2);
12
  
13
  char d[] __attribute__ ((aligned (8))) ="Hallo";
14
  printf("addr von H='%0x', addr von a='%0x', addr von l='%0x', addr von l='%0x', addr von o='%0x'\n",&d[0],&d[1],&d[2],&d[3],&d[4]);
15
}

Ergibt folgende Ausgabe:

1
$ gcc test.c && ./a.out
2

3
a='A', b='B, c='C'
4
addr von a='11c42098', addr von b='11c42090', addr von c='11c42088'
5
p1=11c42098, p2=11c42099
6
addr von H='11c42080', addr von a='11c42081', addr von l='11c42082', addr von l='11c42083', addr von o='11c42084'

Dieses mal wurden die drei char Variablen an 64 Bit Adressen ausgerichtet.

Der Zeiger wurde dennoch um 1 inkrementiert.

Die Zeichen vom Array liegen wieder direkt aufeinander folgend im Speicher.

Um es nochmal klipp und klar zu sagen:

  1. sizeof(char) ist immer 1.
  2. wenn du einen Zeiger auf ein char inkrementierst, dann wird er um 1 erhöht.
  3. in einem Array liegen alle Zeichen direkt hintereinander.
  4. das ist völlig unabhängig vom Alignment.
: Bearbeitet durch User
#8073025
Lesenswert?

Nick schrieb:

Es ging ums allokieren, hast du wohl überlesen.

Ich habe

dass ein char pointer in ein Array nach einem increment 4 Bytes weiter zeigt

gelesen

Ich habe wohl zu viel geschrieben. Hier ein kürzeres Beispiel dass genau diesen einen Fall zeigt:

1
#include <stdio.h>
2
int main() {
3

4
  char d[] __attribute__ ((aligned (8))) ="Hallo";
5

6
  char* p1=d; 
7
  char* p2=p1;
8
  p2++;
9
  printf("addr von d=%0x, p1=%0x, p2=%0x\n",d,p1,p2);
10
}

Ausgabe:

1
$ gcc test.c && ./a.out
2

3
addr von d=90743ee8, p1=90743ee8, p2=90743ee9

p2 ist NICHT "4 Bytes weiter".

Lass uns nochmal auf den vorherigen Punkt zurück kommen, denn ich glaube, dort haben wir ein Missverständnis:

Nick schrieb:

Allokiert man 2 chars in einem array, bleibt es bei den 8 bytes. 4 char array immer noch 8 bytes, 5 char array dann 12 bytes.

War wohl so gemeint:

1
char a[2] __attribute__ ((aligned (4))) ="H";
2
char b[4] __attribute__ ((aligned (4))) ="Hal";
3
char c[5] __attribute__ ((aligned (4))) ="Hall";
4
int i;

Du wolltest wohl darauf hinaus, dass der Compiler hinter den Arrays Platz reserviert, damit die nächste Variable an der nächsten 32 Bit Adresse ausgerichtet ist.

Aber das muss nicht immer sein, denn es könnte andere Variablen geben, die nicht an 32 Bit ausgerichtet sein müssen. Ein char zum Beispiel.

Ich meine bei Cortex-M ist es z.B. so, dass 16 Bit Variablen an 16 Bit Adressen ausgerichtet werden und 32 Bit Variablen an 32 Bit Adressen. Aber 8 Bit Variablen können überall stehen und eben diese Lücken füllen.

: Bearbeitet durch User
#8073034
Lesenswert?

Du hast gleichzeitig den Sarkasmus überlesen

Irgendwas kapiere ich immer noch nicht. Aber egal, davon geht die Welt nicht unter.

Da ich gerade ein STM32 Board mit Cortex-M33 hier liegen habe:

1
#include <stdio.h>
2
#include "stm32c5xx.h"
3
#include "tusb.h"
4

5
...
6

7
char a;
8
char zwei[2];
9
char vier[4];
10
char fuenf[5];
11
char b;
12

13
void main_task(void) {
14

15
            ...
16

17
            char buf[100];
18
            sprintf(buf,"zwei=%0x vier=%0x fuenf=%0x a=%0x b=%0x\n",zwei,vier,fuenf,&a,&b);
19
            tud_cdc_n_write_str(0, buf);
20

21
            ...
22
}

Ausgabe:

1
zwei=2000009c vier=20000098 fuenf=20000090 a=2000009e b=2000008d

Ich bringe das mal in die richtige Reihenfolge:

1
    b=2000008d  3 Bytes incl. 2 Füllbytes, keine 32 Bit Ausrichtung
2
fuenf=20000090  8 Bytes incl. 3 Füllbytes
3
 vier=20000098  4 Bytes, keine Füllbytes
4
 zwei=2000009c  2 Bytes, keine Füllbytes
5
    a=2000009e  keine 32 Bit Ausrichtung

Wie man sieht, sind die Arrays an 32 Bit Grenzen ausgerichtet, die einzelnen chars aber nicht. Und die Größe der Arrays ist (samt Füllbytes) dem zufolge nicht immer ein Vielfaches von 4.

: Bearbeitet durch User
#8073060
Lesenswert?

Nemopuk schrieb:

Und die Größe der Arrays ist (samt Füllbytes) dem zufolge nicht immer ein Vielfaches von 4.

malloc ist dir wirklich kein Begriff? Es ging um die Größe der allokierten (hint: mit malloc) Blöcke. Und die sind üblicherweise aus Performancegründen Vielfache der Wortgröße mit n >= 2. Nochmal: Wenn der Adressraum größer ist, dann ist der pointer auf den Folgeblock nicht von der Wortbreite abhängig.

Das mit dem "Vielfaches von 4" hast du dir unterwegs dazufantasiert.

Ich glaub, ich hab genug dazu gesagt.

#8073068
Lesenswert?

Mir war nicht klar, dass es dir nur um malloc() ging. Ich dachte es ging um das Alignment des Compilers bzw. der CPU.

Die malloc() Funktion der Newlib Nano verwendet offenbar ein 8 Byte Raster.

https://github.com/eblot/newlib/blob/2a63fa0fd26ffb6603f69d9e369e944fe449c246/newlib/libc/stdlib/nano-mallocr.c#L107

(Darüber habe ich mir nie Gedanken gemacht, weil es bisher immer "einfach so" zufriedenstellend funktionierte).

: Bearbeitet durch User
#8073069
Lesenswert?

Nemopuk schrieb:

1
 printf("addr von a='%0x', addr von b='%0x', addr von c='%0x'\n",&a,&b,&c);

Das Formatelement für Zeiger in printf ist '%p', nicht '%0x'. Mit -Wformat gibt der gcc mit Deinem Code eine Warnung aus.

1
print_ptr.c:13:25: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘char *’ [-Wformat=]
2
   13 |   printf("addr von a='%0x', addr von b='%0x', addr von c='%0x'\n",&a,&b,&c);
3
      |                       ~~^                                         ~~
4
      |                         |                                         |
5
      |                         unsigned int                              char *
6
      |                       %0s
#8073549
Lesenswert?

Nick schrieb:

Nemopuk schrieb:

Mir war nicht klar, dass es dir nur um malloc() ging.

Wie oft ich "allokiert" geschrieben hab, kannst du selbst nachzählen.

Auch die Allokation von Speicher auf dem Stack für lokale Variablen durch den Compiler wird durchaus als solche bezeichnet, nicht nur ein malloc auf dem Heap. Versuch dich mal an die Compilerbau-Vorlesungen zurück zu erinnern. ;-)

#8073595
Lesenswert?

Ralf D. schrieb:

Auch die Allokation von Speicher auf dem Stack für lokale Variablen durch den Compiler wird durchaus als solche bezeichnet

Wenn man da Beitrag "Re: Fühlt sich der gcc hier veralbert?" was anderes wie malloc reininterpretiert, muss man schon arg verbohrt sein.

Nick schrieb:

Es ging ums allokieren, hast du wohl überlesen. Also malloc.

Und wenn man es danach immer noch nicht verstanden hat, kann ich auch nicht mehr helfen.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren