Unerwartete Optimierung von strlen beim Aliasing eines 2-D-Arrays


28

Hier ist mein Code:

#include <string.h>
#include <stdio.h>

typedef char BUF[8];

typedef struct
{
    BUF b[23];
} S;

S s;

int main()
{
    int n;

    memcpy(&s, "1234567812345678", 17);

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

    n = strlen((char *)&s) / sizeof(BUF);
    printf("%d\n", n);
}

Wenn Sie gcc 8.3.0 oder 8.2.1 mit einer beliebigen Optimierungsstufe verwenden -O0, wird dies ausgegeben , 0 2wenn ich es erwartet habe 2 2. Der Compiler hat entschieden, dass das strlenan b[0]den Wert, durch den geteilt wird, gebunden ist und ihn daher niemals erreichen oder überschreiten kann.

Ist das ein Fehler in meinem Code oder ein Fehler im Compiler?

Dies ist im Standard nicht klar formuliert, aber ich dachte, die gängige Interpretation der Zeigerherkunft war, dass Xder Code für jedes Objekt (char *)&Xeinen Zeiger generieren sollte , der über die gesamte Iteration iterieren kann X- dieses Konzept sollte auch dann gelten, wenn Xdies der Fall ist Sub-Arrays als interne Struktur.

(Bonusfrage, gibt es ein gcc-Flag, um diese spezielle Optimierung auszuschalten?)



4
Ref: Mein gcc 7.4.0 berichtet 2 2unter verschiedenen Optionen.
chux

2
@Ale die Standardgarantien, dass sie an der gleichen Adresse sind (Struktur kann keine anfängliche Auffüllung haben)
MM

3
@ DavidRankin-ReinstateMonica "was dazu führt, dass die Grenzen von char (*) [8] auf b [0] beschränkt sind. Aber so weit ich komme" denke ich, dass es nagelt. da es auf 8 Zeichen s.bbeschränkt ist, gibt b[0]es zwei Optionen: (1) Zugriff außerhalb der Grenzen, falls 8 Nicht-Null-Zeichen vorhanden sind, dh UB, (2) gibt es ein Null-Zeichen, in dem Die Länge ist kleiner als 8, daher ergibt das Teilen durch 8 Null.
Wenn Sie also den

3
Angesichts von & s == & s.b kann das Ergebnis auf keinen Fall abweichen. Wie @ user2162550 gezeigt hat, wird strlen () nicht aufgerufen und der Compiler errät, was sein Ergebnis sein könnte, selbst in dem Fall godbolt.org/z/dMcrdy, in dem der Compiler es nicht wissen kann. Es ist ein Compiler-Fehler .
Ale

Antworten:


-1

Es gibt einige Probleme, die ich sehen kann und die davon abhängen können, wie sich der Compiler für das Layout des Speichers entscheidet.

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

Im obigen Code s.bbefindet sich ein Array mit 23 Einträgen aus einem Array mit 8 Zeichen. Wenn Sie sich nur auf beziehen, erhalten s.bSie die Adresse des ersten Eintrags im 23-Byte-Array (und das erste Byte im 8-Zeichen-Array). Wenn der Code sagt &s.b, werden Sie nach der Adresse der Adresse des Arrays gefragt. Unter dem Deckmantel generiert der Compiler höchstwahrscheinlich einen lokalen Speicher, speichert die Adresse des Arrays darin und liefert die Adresse des lokalen Speichers an strlen.

Sie haben 2 mögliche Lösungen. Sie sind:

    n = strlen((char *)s.b) / sizeof(BUF);
    printf("%d\n", n);

oder

    n = strlen((char *)&s.b[0]) / sizeof(BUF);
    printf("%d\n", n);

Ich habe auch versucht, Ihr Programm auszuführen und das Problem zu demonstrieren, aber sowohl das Klirren als auch die Version von gcc, die ich mit allen -OOptionen habe, funktionierten immer noch wie erwartet. Für das, was es wert ist, verwende ich Clang Version 9.0.0-2 und GCC Version 9.2.1 unter x86_64-pc-linux-gnu.


-2

Der Code enthält Fehler.

 memcpy(&s, "1234567812345678", 17);

ist zum Beispiel riskant, obwohl s mit b beginnt sollte sein:

 memcpy(&s.b, "1234567812345678", 17);

Das zweite strlen () hat ebenfalls Fehler

n = strlen((char *)&s) / sizeof(BUF);

Zum Beispiel sollte sein:

n = strlen((char *)&s.b) / sizeof(BUF);

Die Zeichenfolge sb sollte bei korrekter Kopie 17 Buchstaben lang sein. Nicht sicher, wie Strukturen im Speicher gespeichert werden, wenn sie ausgerichtet sind. Haben Sie überprüft, ob jdn tatsächlich die 17 kopierten Zeichen enthält?

Ein Strlen (jdn) sollte also 17 zeigen

Der printf zeigt nur Ganzzahlen an, da% d eine Ganzzahl ist und die Variable n als Ganzzahl deklariert ist. sizeof (BUF) sollte 8 sein

Eine 17 geteilt durch 8 (17/8) sollte also 2 ausgeben, da n als Ganzzahl deklariert ist. Da memcpy zum Kopieren von Daten nach s und nicht nach sb verwendet wurde, würde ich vermuten, dass dies mit Speicherausrichtungen zu tun hat. Angenommen, es handelt sich um einen 64-Bit-Computer, kann eine Speicheradresse 8 Zeichen enthalten.

Nehmen wir zum Beispiel an, dass jemand ein Malloc (1) aufgerufen hat, als der nächste "freie Speicherplatz" nicht ausgerichtet ist ...

Der zweite strlen-Aufruf zeigt die korrekte Nummer an, da die Zeichenfolgenkopie in die s-Struktur anstatt in sb durchgeführt wurde

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.