Ottimizzazione imprevista di strlen durante l'aliasing dell'array 2-d


28

Ecco il mio codice:

#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);
}

Usando gcc 8.3.0 o 8.2.1 con qualsiasi livello di ottimizzazione eccetto -O0, questo emette 0 2quando mi aspettavo 2 2. Il compilatore ha deciso che strlenè limitato b[0]e quindi non può mai eguagliare o superare il valore da dividere.

È un bug nel mio codice o un bug nel compilatore?

Questo non è scritto nella norma chiaramente, ma ho pensato che l'interpretazione corrente principale della puntatore provenienza, era che per ogni oggetto X, il codice (char *)&Xdovrebbe generare un puntatore che può iterare su tutto X- questo concetto dovrebbe tenere anche se Xcapita di avere sotto-array come struttura interna.

(Domanda bonus, c'è un flag gcc per disattivare questa specifica ottimizzazione?)



4
Rif: My gcc 7.4.0 riporta 2 2varie opzioni.
chux - Ripristina Monica l'

2
@Ale le garanzie standard sono allo stesso indirizzo (struct non può avere il riempimento iniziale)
MM

3
@ DavidRankin-ReinstateMonica "con il risultato che i limiti di char (*) [8] sono limitati a b [0]. Ma per quanto mi riguarda" Penso che lo inchiodi. poiché s.bè limitato ad b[0]esso è limitato a 8 caratteri, e quindi due opzioni: (1) accesso non associato nel caso in cui vi siano 8 caratteri non nulli, che è UB, (2) c'è un carattere nullo, in cui la len è inferiore a 8, quindi dividendo per 8 si ottiene zero. Quindi, mettendo insieme (1) + (2) il compilatore può usare l'UB per dare lo stesso risultato ad entrambi i casi
user2162550

3
Dato che & s == & s.b, il risultato non può essere diverso. Come ha mostrato @ user2162550, strlen () non viene chiamato e il compilatore fa un'ipotesi su quale potrebbe essere il suo risultato, anche nel caso godbolt.org/z/dMcrdy dove il compilatore non può saperlo. È un bug del compilatore .
Ale

Risposte:


-1

Ci sono alcuni problemi che posso vedere e possono essere influenzati da come il compilatore decide di memoria di layout.

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

Nel codice sopra s.bè un array di 23 voci di un array di 8 caratteri. Quando ti riferisci a solo s.bstai ottenendo l'indirizzo della prima voce nella matrice di 23 byte (e il primo byte nella matrice di 8 caratteri). Quando il codice dice &s.b, questo sta chiedendo l'indirizzo dell'indirizzo dell'array. Sotto le copertine, il compilatore sta molto probabilmente generando un po 'di memoria locale, memorizzando l'indirizzo dell'array e fornendo l'indirizzo della memoria locale strlen.

Hai 2 possibili soluzioni. Loro sono:

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

o

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

Ho anche provato a eseguire il tuo programma e dimostrare il problema, ma sia il clang che la versione di gcc che ho con tutte le -Oopzioni hanno funzionato come previsto. Per quello che vale, sto eseguendo clang versione 9.0.0-2 e gcc versione 9.2.1 su x86_64-pc-linux-gnu).


-2

Ci sono errori nel codice.

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

per esempio, è rischioso, anche se s inizia con b dovrebbe essere:

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

Il secondo strlen () ha anche errori

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

ad esempio, dovrebbe essere:

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

La stringa sb, se copiata correttamente, dovrebbe essere lunga 17 lettere. Non sono sicuro di come le strutture siano archiviate in memoria, se sono allineate. Hai verificato che sb contenga effettivamente i 17 caratteri copiati?

Quindi un strlen (sb) dovrebbe mostrare 17

Printf mostra solo numeri interi, poiché% d è intero e la variabile n viene dichiarata come intero. sizeof (BUF), dovrebbe essere 8

Quindi un 17 diviso 8 (17/8) dovrebbe stampare 2 come n è dichiarato come intero. Dato che memcpy veniva usato per copiare dati su se non su sb, immagino che ciò abbia a che fare con gli allineamenti di memoria; supponendo che si tratti di un computer a 64 bit, ci possono essere 8 caratteri su un indirizzo di memoria.

Ad esempio, supponiamo che qualcuno abbia chiamato un malloc (1), che il successivo "spazio libero" non sia allineato ...

La seconda chiamata strlen, mostra il numero corretto, poiché la copia della stringa è stata eseguita nella struttura anziché in sb

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.