2012-01-16 10 views
12

Ho un pezzo di codice in cui sto cercando di restituire il quadrato del valore indicato da *ptr.Cosa c'è di sbagliato con questo codice C

int square(volatile int *ptr) 
{ 
    int a,b; 
    a = *ptr; 
    b = *ptr; 
    return a * b; 
} 

    main() 
    { 
    int a=8,t; 
    t=square(&a); 
    printf("%d",t); 
    } 

relativo funzionamento benissimo per me, ma autore di questo codice ha detto che potrebbe non funzionare a causa della seguente motivo:
Perché è possibile che il valore di *ptr per cambiare in modo imprevisto, è possibile che a e b di essere diverso. Di conseguenza, questo codice potrebbe restituire un numero che non è un quadrato !. Il modo corretto di fare è

long square(volatile int *ptr) 
{ 
    int a; 
    a = *ptr; 
    return a * a; 
} 

Volevo davvero sapere perché ha detto così?

+0

Se '* ptr' cambia tra l'assegnazione a a l'assegnazione a b, il risultato non è un quadrato. Non ho idea di cosa causerà '* ptr' per cambiare. – Sjoerd

+2

L'unica cosa che posso pensare in un ambiente multi-thread il contenuto di * ptr potrebbe cambiare in un altro thread. In questo caso, potrebbe avere un valore diverso da b. – Totonga

+1

La tua seconda versione ha una firma fuorviante; 'a * a' è un' int', e la conversione implicita a 'long' arriva troppo tardi per aumentare la precisione del valore di ritorno. Per risolvere il problema, dovresti dichiarare 'a' come' long'. (Naturalmente, su molti sistemi 'long' e' int' sono comunque sinonimi.) – ruakh

risposta

10

L'idea della parola chiave volatile è esattamente per indicare al compilatore che una variabile contrassegnato come tale può cambiare in modo inaspettato durante l'esecuzione del programma.

Tuttavia, questo non lo rende una fonte di "numeri casuali" - si limita a consigliare il compilatore - ciò che è responsabile della modifica effettiva del contenuto della variabile dovrebbe essere un altro processo, thread, qualche interrupt hardware - qualsiasi cosa che scriverebbe a la memoria del processo ma non è stata inserita nella funzione in cui si trova la dichiarazione volatile. Nei "vecchi tempi" (compilatori con meno magia) tutto ciò che faceva impediva al compilatore di memorizzare nella cache il valore della variabile in uno dei registri della CPU. Non ho idea delle strategie di ottimizzazione/de-ottimismo innescate dai moderni compilatori - ma almeno lo farà.

In assenza di tali fattori esterni, una variabile "volatile" è uguale a qualsiasi altra. In realtà - è proprio come qualsiasi altra variabile - poiché le variabili non contrassegnate come volatili possono anche essere modificate dalle stesse cause esterne (ma il codice C compilato non sarebbe preparato per questo in questo caso, il che potrebbe portare a utilizzare valori errati) .

+2

Tenere in corto con i fili. L'uso della variabile 'volatile' per la sincronizzazione dei thread è un ** bug ** che arriverà a farti ottenere un giorno. Vedi [N2016] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html) per la discussione perché 'volatile' non ha acquisito la semantica per la sincronizzazione dei thread in C++ 11. –

+0

'volatile' può essere una parte di una soluzione thread-safe. Anche una parte importante. Ma ci vuole una vera esperienza per sapere esattamente come, quando e dove. I ragazzi che scrivono primitive di sincronizzazione hanno questa competenza e noi programmatori comuni dovremmo semplicemente usare il loro lavoro. – ugoren

2

Se è presente più di un thread, il valore indicato dal puntatore può variare tra l'istruzione "a = * ptr" e l'istruzione "b = * ptr". Inoltre: vuoi il quadrato di un valore, perché metterlo in due variabili?

5

prima capire che cosa è volatile: Why is volatile needed in C?

e poi, cercare di trovare la risposta da soli.

È un gioco di mondo volatile e hardware. :-)

Leggi risposta data dal Chris Jester-Young:

volatili dice al compilatore che la variabile può essere modificato con altri mezzi, oltre il codice che accede a esso. ad esempio, potrebbe essere una posizione di memoria mappata I/O. Se questo non è specificato in questi casi, alcuni accessi variabili possono essere ottimizzati, ad esempio, il suo contenuto può essere tenuto in un registro e la posizione di memoria non viene nuovamente riletta.

+1

motivo per downvote? – Azodious

+0

In effetti "motivo di downvote?" (/ me upvoted) - questa è una delle poche risposte qui che effettivamente risolve il problema: l'uso della parola chiave "volatile". – jsbueno

2

Nel codice si presenti allora non c'è modo per la variabile a che viene definito nel main essere modificato mentre square è in esecuzione.

Tuttavia, prendere in considerazione un programma multi-thread. Supponiamo che un altro thread abbia modificato il valore del tuo puntatore. Supponiamo che questa modifica sia avvenuta dopo aver assegnato a, ma prima di aver assegnato b, nella funzione sqaure.

int square(volatile int *ptr) 
{ 
    int a,b; 
    a = *ptr; 
    //the other thread writes to *ptr now 
    b = *ptr; 
    return a * b; 
} 

In questo scenario, a e b sarebbe avere valori diversi.

0

Poiché il valore del puntatore * ptr potrebbe cambiare tra il primo affetto e il secondo.

1

L'autore è corretta (se * ptr sarà cambiato da altri thread)

int square(volatile int *ptr) 
{ 
    int a,b; 
    a = *ptr; 
    //between this two assignments *ptr can change. So it is dangerous to do so. His way is safer 
    b = *ptr; 
    return a * b; 
} 
8

Poiché la domanda ha una risposta accettata e corretta, sarò breve: ecco un breve programma che puoi eseguire per vedere il comportamento scorretto che si verifica per te.

#include <pthread.h> 
#include <math.h> 
#include <stdio.h> 

int square(volatile int *p) { 
    int a = *p; 
    int b = *p; 
    return a*b; 
} 

volatile int done; 

void* call_square(void* ptr) { 
    int *p = (int*)ptr; 
    int i = 0; 
    while (++i != 2000000000) { 
     int res = square(p); 
     int root = sqrt(res); 
     if (root*root != res) { 
      printf("square() returned %d after %d successful calls\n", res, i); 
      break; 
     } 
    } 
    done = 1; 
} 

int main() { 
    pthread_t thread; 
    int num = 0, i = 0; 
    done = 0; 
    int ret = pthread_create(&thread, NULL, call_square, (void*)&num); 
    while (!done) { 
     num = i++; 
     i %= 100; 
    } 
    return 0; 
} 

La funzione main() genera un filo, e modifica i dati che vengono squadrati in un ciclo in concomitanza con un altro ciclo chiamando il square con un puntatore volatile. Relativamente parlando, non manca spesso, ma lo fa in modo molto affidabile in meno di un secondo:

square() returned 1353 after 5705 successful calls <<== 1353 = 33*41 
square() returned 340 after 314 successful calls <<== 340 = 17*20 
square() returned 1023 after 5566 successful calls <<== 1023 = 31*33 
+0

La "magia" scompare se anche l'ottimizzazione '-O1' è abilitata in gcc. La parola chiave "volatile" dovrebbe essere utilizzata per impedire l'ottimizzazione delle variabili "critiche". Ci sono due variabili di questo tipo nell'esempio precedente: 'int * p' in' square' per forzare il dereferenziamento 'p' due volte e anche indicato da' p' 'int num' in' main() 'deve essere' volatile'. –

0

Non credo che il valore di * ptr può cambiare in questo codice di blocco estremamente insolito (e non standard di runtime conforme agli standard).

Qui stiamo esaminando l'intero main() e non si avviano altri thread. La variabile a, il cui indirizzo stiamo prendendo, è un locale in main() e main() non informa alcuna altra funzione dell'indirizzo di quella variabile.

Se è stato aggiunto alla linea mysterious_external_function(&a); prima della linea t=square(&a), allora sì, mysterious_external_function potrebbe iniziare un filo e diddle la variabile a in modo asincrono. Ma non esiste una tale linea, così come scritto square() restituisce sempre un quadrato.

(era il PO un post troll, a proposito?)

0

Vedo alcune risposte con * PTR possono essere modificati da altri thread. Ma questo non può accadere poiché * ptr non è una variabile di dati statici. È una variabile di parametro e le variabili locali e dei parametri sono contenute nello stack. Ogni thread ha una propria sezione stack e se * ptr è stato modificato da un altro thread, non dovrebbe avere effetto sul thread corrente.

Un motivo per cui il risultato potrebbe non dare al quadrato può essere un interrupt HW potrebbe accadere prima di assegnare b = * ptr; operazione come indicato di seguito:

int square(volatile int *ptr) { 
    int a,b; 
    a = *ptr; //assuming a is being kept inside CPU registers. 

    //an HW interrupt might occur here and change the value inside the register which keeps the value of integer "a" 

    b = *ptr; 
    return a * b; 
} 
+0

Sbagliato. La variabile 'ptr' è locale. Tuttavia punta a qualche indirizzo. Quell'indirizzo può puntare a qualche variabile su stack di altro thread o anche a una variabile statica. Il valore 'ptr' non può essere modificato, tuttavia la variabile puntata da' ptr' non è locale e può essere modificata. Quindi, '* ptr' può essere scartato. –