2012-01-16 236 views
12

我有一段代碼,我試圖返回*ptr指向的值的平方。這個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); 
    } 

它爲我,但這段代碼的作者工作正常,說可能不是因爲以下原因工作:因爲它可能爲*ptr值意外改變
,有可能A和B是不同。因此,這段代碼可能會返回一個不是正方形的數字!正確的方法是

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

我真的很想知道他爲什麼這麼說?

+0

如果'* ptr'在賦值給a和賦值給b之間改變,結果不是一個正方形。我不知道什麼會導致'* ptr'改變。 – Sjoerd 2012-01-16 12:42:15

+2

在多線程環境中,我能想到的只有* ptr的內容可以在另一個線程中更改。在這種情況下,a可能與b有不同的值。 – Totonga 2012-01-16 12:44:50

+1

你的第二個版本有誤導性的簽名; 'a * a'是一個'int',隱式轉換爲'long'對於提高返回值的精度來說太遲了。爲了解決這個問題,你應該把'a'聲明爲'long'。 (當然,在很多系統中,long和int都是同義詞。) – ruakh 2012-01-16 14:25:51

回答

10

volatile關鍵字的想法是完全以指示變量標註,可以以意想不到的方式改變編譯器在程序執行期間。

但是,這並不會使它成爲「隨機數」的來源 - 它只是建議編譯器 - 實際更改變量內容的責任應該是另一個進程,線程,某個硬件中斷 - 任何會寫入進程內存,但不在易失性聲明發現自己的函數中內聯。在「較舊的時代」(編譯器不太神奇),它所做的一切都是阻止編譯器將一個CPU寄存器中的變量值緩存起來。我不知道現代編譯器引發的優化/去優化策略 - 但至少會這樣做。

在沒有任何這樣的外部因素的情況下,「易變」變量就像任何其他變量一樣。實際上 - 就像任何其他變量一樣 - 因爲沒有標記爲volatile的變量也可以通過相同的外部原因來改變(但是在這種情況下編譯的C代碼不會被準備好,這可能導致使用不正確的值) 。

+2

與線程保持一致。使用'volatile'變量進行線程同步是一個** bug **,它會在某一天得到幫助。參見[N2016](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html)討論爲什麼'volatile'沒有獲取C語言中線程同步的語義++ 11。 – 2012-01-16 18:14:56

+0

'volatile'可以成爲線程安全解決方案的一部分。即使是重要的一部分。但它需要真正的專業知識來確切知道如何,何時和在哪裏。編寫同步原語的人有這方面的專業知識,而我們普通的程序員應該只使用他們的工作。 – ugoren 2012-01-16 18:44:49

2

如果存在多個線程,則指針指向的值可能會在語句「a = * ptr」和語句「b = * ptr」之間改變。另外:你想要一個值的平方,爲什麼把它分成兩個變量?

5

先了解什麼是揮發性:Why is volatile needed in C?

,然後嘗試着自己找到答案。

這是一個易變和硬件世界的遊戲。通過克里斯小丑,楊給出 :-)

閱讀答案:

揮發性告訴你的變量可以通過其他方式來改變,比正在訪問它的代碼編譯器。例如它可以是I/O映射的存儲器位置。如果在這種情況下沒有指定它,則可以優化一些變量訪問,例如,其內容可以保存在寄存器中,並且內存位置不會再次讀回。

+1

downvote的原因? – Azodious 2012-01-16 12:47:02

+0

確實是「downvote的原因?」 (/我upvoted) - 這是這裏幾個實際解決問題的答案之一 - 使用「volatile」關鍵字。 – jsbueno 2012-01-16 12:50:06

2

在你目前的代碼,那麼有沒有辦法,在您main定義,同時square運行被修改爲變量a

但是,考慮一個多線程程序。假設另一個線程修改了你的指針引用的值。並且假設此修改發生在您分配了a之後,但在分配b之前發生在功能sqaure中。

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

在這種情況下,ab將有不同的值。

0

因爲指針* ptr的值可能會在第一個情感和第二個情感之間改變。

1

作者是正確的(如果* PTR會被其他線程改變)

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

由於問題具有可接受的正確答案,因此我將簡要介紹一下:這裏是一個簡短的程序,您可以運行該程序查看自己發生的不正確行爲。

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

main()功能產生一個線程,並修改在一個循環中被平方與另一種環調用square用揮發性指針的數據。相對來說,它不經常失敗,但它這樣做非常可靠不到一秒鐘:

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

即使在gcc中啓用了優化「-O1」,'魔術'也會消失。應該使用'volatile'關鍵字來防止「關鍵」變量的優化。在上面的例子中有兩個這樣的變量:'square'中的'int * p'強制解除引用'p'兩次,並且'main()'中的'p'' int num'也必須是'volatile'。 – 2015-09-27 22:10:09

0

我不認爲* PTR的值可以在此代碼除非出現極不尋常的改變(與非符合標準的)運行時環境。

我們正在查看整個main()這裏,它並沒有啓動其他線程。變量a(我們正在使用的地址)是main()中的本地變量,而main()不通知該變量地址的任何其他函數。

如果您在t=square(&a)行之前添加行mysterious_external_function(&a);,那麼,mysterious_external_function可以啓動一個線程和異步騙取了a變量。但是沒有這樣的路線,所以書面square()總是返回一個正方形。

(當時的OP一個巨魔後,順便?)

0

我看到一些答案與* PTR可以被其他線程改變。但是這不會發生,因爲* ptr不是一個靜態數據變量。它的參數變量和局部參數變量被保存在棧中。每個線程都有自己的堆棧段,如果* ptr已被另一個線程改變,它不應該影響當前線程。

爲什麼結果不能給出正方形的一個原因可能是在分配b = * ptr之前可能發生硬件中斷;操作如下所示:

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

錯了。變量'ptr'是本地的。但它指向某個地址。該地址可能指向其他線程堆棧上的一些變量,甚至指向一個靜態變量。值'ptr'不能改變,但'ptr'指向的變量不是本地的,它可以改變。所以,'* ptr'可以被破解。 – 2015-09-27 21:27:06