2015-07-20 23 views
5

Quindi, recentemente mi sono interessato a quanto bene il compilatore (gcc (GCC) 4.8.3 è quello in questione) sta ottimizzando i puntatori e i puntatori.Quando il compilatore dovrebbe essere prudente riguardo all'ottimizzazione della dereferenziazione del puntatore, se non del tutto?

Inizialmente ho creato un intero semplice e un puntatore intero e realizzato le operazioni su di esso in modo da poterlo stampare. Come previsto, tutte le operazioni che sono state codificate in modo rigido sono state ottimizzate, tramite il puntatore dereferenziato o meno.

call __main 
leaq .LC0(%rip), %rcx 
movl $1, %edx 
call printf 

E anche dopo la creazione di una funzione che prende in un puntatore a int, dereferenziazioni IT e cambia ancora era perfettamente optmized.

call __main 
leaq .LC0(%rip), %rcx 
movl $-1, %edx 
call printf 

Ora, quando ho trattato il mio puntatore come un vuoto e apportato delle modifiche gettandola a char e dereferenziazione, è in realtà ancora optmized perfettamente (una chiamata di 'extra' mov da quando ho inizialmente trattato come un 8 byte valore, e quindi come valore 1 byte per puntatore dereferenziazione)

call __main 
movl $4, 44(%rsp) 
movb $2, 44(%rsp) 
leaq .LC0(%rip), %rcx 
movl 44(%rsp), %eax 
leal 1(%rax), %edx 
call printf 

quindi sulla mia domanda (s):

  1. Quanto è coerente l'ottimizzazione del compilatore per quanto riguarda la dereferenziazione del puntatore? Quali sarebbero i casi in cui avrebbe scelto di essere conservatore?

  2. Se tutti i miei indicatori in un progetto sono stati dichiarati con la parola chiave restrict, potrei fidarmi che sarebbe ottimizzato come se "nessun puntatore fosse utilizzato"?

(assumendo non ci sono volatile casi)

Ps¹ .: Sono consapevole che il compilatore fa in genere un lavoro abbastanza buono, e che un programmatore preoccuparsi aiutare il compilatore in ottimizzazioni minori è, in generico, improduttivo (come molti indicano nello stackoverflow risponde alle domande relative all'ottimizzazione). Eppure ho ancora curiosità riguardo alla questione.

Ps² .: gcc -O3 -S -c main.c è stato il comando utilizzato per generare il codice assembly

Codice C: (come richiesto)

1:

#include <stdio.h> 

int main (void) 
{ 
    int a = 4; 
    int *ap = &a; 

    *ap = 0; 
    a += 1; 

    printf("%d\n", a); 
    return 0; 
} 

2:

#include <stdio.h> 

void change(int *p) { 
    *p -= 2; 
} 

int main (void) 
{ 
    int a = 4; 
    int *ap = &a; 

    *ap = 0; 
    change(ap); 
    a += 1; 

    printf("%d\n", a); 
    return 0; 
} 

3:

#include <stdio.h> 

void change(void *p) { 
    *((char*)p) += 2; 
} 

int main (void) 
{ 
    int a = 4; 
    void *ap = (void*) &a; 

    *((char*)(ap)) = 0; 
    change(ap); 
    a += 1; 

    printf("%d\n", a); 
    return 0; 
} 
+3

'restrict' e' const' sono garanzie dal programmatore, non dal compilatore. Se interrompi il contratto, non sorprenderti a invocare UB – Olaf

+0

Perché non includere anche il codice C che ti ha generato l'assembly che hai registrato? –

+0

@JimBuck li ho aggiunti alla domanda. – SSWilks

risposta

1

LLVM e GCC emettono entrambi un codice modulo statico a singolo assenso come parte dell'analisi di ottimizzazione. Una delle proprietà utili del codice SSA è che mostra con precisione il flusso di influenza per l'assegnazione, ovvero sa quali assegnazioni portano ad altri compiti e così può rilevare quali valori possono influenzare tutti gli altri.

La prima catena influenza sembra qualcosa di simile

un -> costante (0) -> ap -> un

La seconda: un -> costante (0) -> ap -> p -> a

Il terzo è molto simile al secondo. (Scusa, questa notazione è praticamente inventata ma spero che illustri il mio punto.)

Poiché è abbastanza semplice dimostrare che l'influenza di un messaggio è deterministica, si sentirà libera di dereferenziare "presto" e combinare le istruzioni in una (sebbene nei primi due casi questa non sia l'affermazione più accurata poiché la costante sovrascrive il riferimento originale e lascia che il compilatore dimostri che l'assegnazione originale non scorre alla fine del codice.

Fare in modo che il compilatore sia più prudente riguardo al dereferenziamento comporterebbe un processo abbastanza complicato da sfuggire alla comprensione del compilatore (difficile in un programma statico credo) o più probabilmente inducendo il compilatore a invocare una funzione phi nel processo di SSA (in termini laici, a c ause il compito di essere influenzato da più incarichi precedenti) in modo non deterministico.

La parola chiave restrict ha lo scopo di suggerire al compilatore che due puntatori sono diversi. Ciò non limiterebbe l'uso della dereferenziazione in fase di esecuzione se il codice che ha prodotto quel puntatore aveva ancora un'origine non deterministica (ad esempio, se i dati creati in runtime influenzavano la scelta di quale valore del puntatore era dereferenziato- penso che questo potrebbe accadere se un serializzato puntatore è stato inviato al programma da una fonte esterna?)

Problemi correlati