2015-05-22 17 views
5

Supponiamo di avere il seguente codice:Limitare l'accesso di campo tra due oggetti dello stesso tipo in gcc

typedef struct { 
    int f1; 
    int f2; 
} t_str; 

int f(t_str* p, t_str* q) 
{ 
    p[0].f1++; 
    q[0].f2++; 
    p[0].f1++; 
    q[0].f2++; 
    p[0].f1++; 
    q[0].f2++; 
    p[0].f1++; 
    q[0].f2++; 
    p[0].f1++; 
    q[0].f2++; 
    p[0].f1++; 
    q[0].f2++; 
    p[0].f1++; 
    q[0].f2++; 
    p[0].f1++; 
    q[0].f2++; 
    p[0].f1++; 
    q[0].f2++; 

return 0; 
} 

Quando abbiamo compilarlo (io ho usato gcc-5.1.0) con -O3 opzione, il compilatore ottiene il seguente assembler:

f: 
.LFB0: 
    .cfi_startproc 
    movl 8(%esp), %edx 
    movl 4(%esp), %ecx 
    movl 4(%edx), %eax 
    addl $9, (%ecx) 
    addl $9, %eax 
    movl %eax, 4(%edx) 
    xorl %eax, %eax 
    ret 
    .cfi_endproc 

ciò significa che gcc ha deciso l'accesso al campo di f1 p e l'accesso al campo di f2 q Non alias. Immagino che ciò derivi dal presupposto che due oggetti dello stesso tipo non si sovrappongano mai o che siano uguali. Ma non ho trovato il problema in standard.

Quindi, per favore, qualcuno può trovare questo problema in standard, o un altro punto perché gcc ha limitato l'accesso al campo, o commenta cosa è successo?

UPD:

Beh, ho pensato a paragrafo 7 della sezione 6.5 troppo, ma sarebbe più comodo per me avere una cosa del genere in forma esplicita per tutti gli oggetti:

6,5. 16.1 semplice assegnazione

3 Se il valore essere memorizzato in un oggetto viene letto da un altro oggetto che si sovrappone in alcun modo la memorizzazione del primo oggetto, quindi la sovrapposizione è esatto ei due oggetti ha qualificato o versioni non qualificate di un tipo compatibile; in caso contrario, il comportamento è indefinito.

Purtroppo questa regola non può essere utilizzata qui.

ora guarda, se il codice sopra faccio la seguente funzione:

void main() 
{ 
    char * c = malloc(12); 
    memset(c, 0, 12); 
    f((t_str *)(c + 4), (t_str *)c); 
    printf("%d %d %d\n", ((t_str *)c)->f1, ((t_str *)c)->f2, ((t_str *)(c + 4))->f2); 
} 

Ora ho il seguente durante l'esecuzione:

$ gcc-5.1.0 test1.c -O3 && ./a.out 
0 9 0 
$ gcc-5.1.0 test1.c -O0 && ./a.out 
0 18 0 

Quindi, come pensi che sia questo codice è valido? Perché non sono sicuro se è conforme all'Articolo 7 della Sezione 6.5.

PS: cosa interessante:

$ gcc-5.1.0 test1.c -O3 -fwhole-program && ./a.out 
0 10 0 
$ gcc-5.1.0 test1.c -O3 -flto && ./a.out 
0 10 0 
+0

Non sono sicuro di aver capito la tua domanda. Cosa intendi con "alias" e "sovrapposizione"? Che cosa sembra il compilatore sembra corretto, purché p e q non cambino tra gli accessi (ad esempio attraverso la gestione di un interrupt o giù di lì). Penso che potresti usare la parola chiave "volatile" per forzare il compilatore ad assumere che potrebbero essere cambiati. –

+0

Per ulteriore divertimento, guarda cosa succede se dichiari 'int * pf1 = &p->f1; int * pf2 = &p->f2;' etc e ora sostituisci 'p-> f1 ++' con '(* pf1) ++'. –

+0

alexanius si chiede se lo Standard escluda esplicitamente la possibilità per 'q' di puntare a' p-> f2'. Poiché la struttura 't_str' ha solo 2 membri' int', una matrice di 2 tali strutture potrebbe essere vista come contenente una terza di tali strutture all'indirizzo '& p-> f2'.Faccio finta che sia escluso, ma potrebbe essere più difficile creare un caso simile per 'typedef struct {int f [2]; } t_str; ' – chqrlie

risposta

3

paragrafo 7 della sezione 6.5 della più recente progetto (N1570) di C11 si legge:

Un oggetto deve avere il suo valore memorizzato accessibile solo da un'espressione lvalue che ha uno dei seguenti tipi: 88) - un tipo compatibile con il tipo effettivo dell'oggetto, - una versione qualificata di un tipo compatibile con il tipo effettivo dell'oggetto, - un tipo che è firmato o non firmato tipo corrispondente all'effe tipo di oggetto dell'oggetto, - un tipo che è il tipo firmato o senza segno corrispondente a una versione qualificata del tipo effettivo dell'oggetto, - un tipo di aggregato o unione che include uno dei tipi sopra menzionati tra i suoi membri (incluso, in modo ricorsivo, un membro di un'unione subaggregata o contenuta) o - un tipo di carattere.

Interpreto questo per significare che gli oggetti puntati da p e q non possono sovrapporsi meno che non siano lo stesso oggetto, perché gli t_str oggetti dovrebbero essere accessibili da puntatori corretti.

lo standard non è abbastanza preciso per mettere in chiaro che &p->f2 non è un puntatore valido a un oggetto t_str composto da 2 int condivisa tra p[0] e p[1]. Tuttavia sembra errato perché il compilatore potrebbe inserire padding tra f1 e f2 o tra f2 e la fine della struttura.

Per inciso, &p->f2 - &p->f1 non è un'espressione valida perché il paragrafo 9 della sezione 6.5.6 Additivo operatori stati questo vincolo: Quando due puntatori vengono sottratti, entrambi devono indicare elementi dello stesso oggetto array, oppure uno dopo l'ultimo elemento dell'oggetto dell'array;

Se funzione f() preso un puntatore a char come parametri e dati accessibili tramite questo puntatore, gcc non potrebbe ipotizzare questi dati per essere distinti dagli int parte delle strutture puntati da p e q. Questa eccezione un po 'controintuitiva è la ragione per cui così tanti prototipi di funzioni della libreria C hanno qualificatori restrict su molti argomenti del puntatore. (Queste qualificazioni nei prototipi di funzioni sono solo un suggerimento per il programmatore, ma in realtà non dicono nulla al compilatore).

+0

Grazie, ho aggiornato la domanda originale – alexanius

0

Come Chqrlie ha menzionato, è improbabile che gcc ti consenta di creare una situazione in cui &p[0].f1 == &q[0].f2. Detto questo, non dovrebbe fare la differenza. Queste operazioni permutano, matematicamente. E l'unico modo in cui scomporre il codice è se c'è un overflow di interi, dove ancora l'ordine non ha importanza. Ho provato questo, e per il mio compilatore (gcc 3.4.6), questa ottimizzazione appare non appena accendo qualsiasi ottimizzazione.

Come contrappunto, consideriamo cosa succede quando si cambia la struttura per

typedef struct { 
    double f1; 
    double f2; 
} t_str; 

In questo caso, anche con -O3, non vediamo gli incrementi di essere combinati insieme, perché siamo riusciti a ottenere risposte diverse dovute underflow.

+0

Mentre sono d'accordo che le operazioni si spostano nella sorgente C, il asm prodotto da 'gcc' spezza questo caricando'% eax' prima di incrementare '(% ecx)' e memorizzando '% eax + 9' ​​successivamente. Un asm più regolare con 'addl $ 9, (% edx)' non darebbe per scontato che '* p' e' * q' non si sovrappongano. – chqrlie

Problemi correlati