2012-01-16 36 views
12

Tôi có một đoạn mã mà tôi đang cố gắng trả lại hình vuông của giá trị được trỏ đến bởi *ptr.Điều gì sai với mã C này

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

của nó làm việc tốt cho tôi, nhưng tác giả của mã này cho biết nó có thể không hoạt động vì lý do sau:
Bởi vì nó có thể cho giá trị của *ptr thay đổi bất ngờ, nó có thể cho a và b là khác nhau. Do đó, mã này có thể trả về một số không phải là hình vuông !. Cách chính xác để làm là

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

Tôi thực sự muốn biết lý do tại sao anh ấy nói như vậy?

+0

Nếu '* ptr' thay đổi giữa chuyển nhượng và chuyển nhượng cho b, kết quả không phải là hình vuông. Tôi không biết điều gì sẽ khiến '* ptr' thay đổi. – Sjoerd

+2

Chỉ có điều mà tôi có thể nghĩ về điều đó trong môi trường đa luồng nội dung của * ptr có thể thay đổi trong một chủ đề khác. Trong trường hợp này, giá trị có thể khác với b. – Totonga

+1

Phiên bản thứ hai của bạn có chữ ký gây hiểu lầm; 'a * a' là' int', và chuyển đổi ẩn thành 'long' đến quá muộn để tăng độ chính xác của giá trị trả về. Để khắc phục điều đó, bạn nên khai báo 'a' là' long'. (Tất nhiên, trên nhiều hệ thống 'long' và' int' là đồng nghĩa.) – ruakh

Trả lời

10

Ý tưởng về từ khóa volatile là chính xác để chỉ cho trình biên dịch rằng một biến đánh dấu như vậy có thể thay đổi theo những cách bất ngờ trong quá trình thực hiện chương trình.

Tuy nhiên, điều đó không làm cho nó trở thành nguồn "số ngẫu nhiên" - nó chỉ khuyên trình biên dịch - những gì có trách nhiệm thay đổi nội dung biến phải là một quá trình, luồng, phần cứng khác. bộ nhớ quá trình nhưng không được gạch chân trong hàm mà khai báo dễ bay hơi tự tìm thấy. Trong "thời gian cũ" (các trình biên dịch có ít phép thuật hơn) mọi thứ nó đã làm ngăn cản trình biên dịch lưu vào bộ nhớ đệm giá trị biến trong một trong các thanh ghi CPU. Tôi không có ý tưởng về các chiến lược tối ưu hóa/de-lạc quan được kích hoạt bởi nó bởi các trình biên dịch hiện đại - nhưng ít nhất nó sẽ làm điều đó.

Trong trường hợp không có bất kỳ yếu tố bên ngoài nào như vậy, biến "dễ bay hơi" cũng giống như bất kỳ biến số nào khác. Trên thực tế - nó cũng giống như bất kỳ biến nào khác - vì các biến không được đánh dấu là biến động cũng có thể được thay đổi bởi cùng các nguyên nhân bên ngoài (nhưng mã C đã biên dịch sẽ không được chuẩn bị cho điều đó trong trường hợp này, có thể dẫn đến các giá trị không chính xác được sử dụng) .

+2

Giữ ngắn với chủ đề. Sử dụng biến 'volatile' để đồng bộ hóa luồng là một lỗi ** sẽ đến để giúp bạn một ngày. Xem [N2016] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html) để thảo luận tại sao 'volatile' không thu được ngữ nghĩa cho đồng bộ hóa chuỗi trong C++ 11. –

+0

'volatile' có thể là một phần của giải pháp an toàn cho luồng. Ngay cả một phần quan trọng. Nhưng nó có chuyên môn thực sự để biết chính xác như thế nào, khi nào và ở đâu. Những kẻ viết các nguyên tắc đồng bộ hóa có chuyên môn này, và chúng ta thường sử dụng các lập trình viên của họ. – ugoren

2

Nếu có nhiều hơn một chuỗi, giá trị mà con trỏ trỏ tới có thể thay đổi câu lệnh "a = * ptr" và câu lệnh "b = * ptr". Ngoài ra: bạn muốn hình vuông của một giá trị, tại sao đặt nó thành hai biến?

5

Đầu tiên hiểu những gì là dễ bay hơi: Why is volatile needed in C?

và sau đó, cố gắng tìm câu trả lời của chính mình.

Đó là trò chơi về thế giới phần cứng và dễ bay hơi. :-)

đọc câu trả lời được đưa ra bởi Chris Jester-Young:

ổn định cho trình biên dịch rằng biến của bạn có thể được thay đổi bằng các phương tiện khác, so với các mã mà được truy cập vào nó. ví dụ: đó có thể là vị trí bộ nhớ được ánh xạ I/O. Nếu điều này không được chỉ định trong các trường hợp như vậy, một số truy cập biến có thể được tối ưu hóa, ví dụ: nội dung của nó có thể được giữ trong sổ đăng ký và vị trí bộ nhớ không đọc lại.

+1

lý do cho downvote? – Azodious

+0

Thật vậy "lý do cho downvote?" (/ tôi upvoted) - đây là một trong số ít câu trả lời ở đây thực sự giải quyết vấn đề - việc sử dụng từ khóa "dễ bay hơi". – jsbueno

2

Trong đoạn code bạn trình bày thì không có cách nào cho biến a được xác định trong main của bạn phải được sửa đổi trong khi square đang chạy.

Tuy nhiên, hãy xem xét chương trình nhiều luồng. Giả sử rằng một luồng khác đã sửa đổi giá trị thành con trỏ của bạn. Và giả sử rằng sửa đổi này diễn ra sau khi bạn đã gán a, nhưng trước khi bạn đã gán b, trong hàm sqaure.

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

Trong trường hợp này, ab sẽ có các giá trị khác nhau.

0

Bởi vì giá trị của con trỏ * ptr có thể thay đổi giữa tình cảm đầu tiên và thứ hai.

1

Tác giả là đúng (nếu * ptr sẽ được thay đổi bằng cách đề khác)

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

Vì câu hỏi có câu trả lời được chấp nhận và chính xác, tôi sẽ tóm tắt: đây là một chương trình ngắn mà bạn có thể chạy để xem hành vi không chính xác xảy ra cho chính mình.

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

Các main() chức năng sinh ra một chủ đề, và sửa đổi các dữ liệu được phương trong một vòng lặp đồng thời với một vòng lặp gọi square với một con trỏ dễ bay hơi. Nói tương đối, nó không thất bại thường xuyên, nhưng nó làm như vậy rất đáng tin cậy trong vòng chưa đầy một giây:

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

'ma thuật' biến mất nếu ngay cả tối ưu hóa '-O1' được bật trong gcc. Từ khóa 'volatile' nên được sử dụng để ngăn chặn sự tối ưu của các biến 'quan trọng'. Có hai biến như vậy trong ví dụ trên: 'int * p' trong' square' để buộc dereferencing 'p' hai lần và cũng được trỏ bởi' p' 'int num' trong' main() 'phải là' volatile'. –

0

Tôi không nghĩ rằng giá trị của * ptr có thể thay đổi trong mã này ngăn cản một cách vô cùng khác thường (và không môi trường thời gian chạy tương thích).

Chúng tôi đang xem toàn bộ main() tại đây và không bắt đầu các chủ đề khác. Biến số a, có địa chỉ chúng tôi đang dùng, là địa phương ở số main()main() không thông báo cho bất kỳ chức năng nào khác về địa chỉ của biến đó.

Nếu bạn đã thêm dòng mysterious_external_function(&a); trước dòng t=square(&a), thì có, mysterious_external_function có thể bắt đầu một chuỗi và cắt biến số a không đồng bộ. Nhưng không có dòng như vậy, do đó, bằng văn bản square() luôn trả về một hình vuông.

(Đã OP một bài troll, bằng cách này?)

0

tôi thấy một số câu trả lời với * ptr có thể được thay đổi bằng cách đề khác. Nhưng điều này không thể xảy ra vì * ptr không phải là biến dữ liệu tĩnh. Biến tham số của nó và biến cục bộ và tham số được giữ bên trong ngăn xếp. Mỗi luồng có phần ngăn xếp riêng của nó và nếu * ptr đã bị thay đổi bởi một luồng khác, nó sẽ không ảnh hưởng đến chuỗi hiện tại.

Một lý do tại sao kết quả có thể không cung cấp cho hình vuông có thể là ngắt CTNH có thể xảy ra trước khi gán b = * ptr; hoạt động như được chỉ ra bên dưới:

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

Sai. Biến 'ptr' là cục bộ. Tuy nhiên nó chỉ vào một số địa chỉ. Địa chỉ đó có thể trỏ đến một số biến trên stack của thread khác hoặc thậm chí là một biến tĩnh. Giá trị 'ptr' không thể thay đổi, tuy nhiên biến được chỉ bởi' ptr' không phải là địa phương và nó có thể được thay đổi. Vì vậy, '* ptr' có thể được dàn dựng. –

Các vấn đề liên quan