Đó không thực sự là quyền lợi ; bạn không chạy một chức năng hai lần trong cùng một luồng (hoặc trong các luồng khác nhau). Bạn có thể có được điều đó thông qua đệ quy hoặc chuyển địa chỉ của hàm hiện tại dưới dạng con trỏ hàm gọi lại arg sang hàm khác. (Và nó sẽ không an toàn vì nó sẽ đồng bộ).
Đây chỉ là cuộc đua dữ liệu vanilla đơn giản UB (Hành vi không xác định) giữa trình xử lý tín hiệu và luồng chính: chỉ sig_atomic_t
được đảm bảo an toàn cho việc này . Các trường hợp khác có thể xảy ra để hoạt động, như trong trường hợp của bạn khi một đối tượng 8 byte có thể được tải hoặc lưu trữ với một lệnh trên x86-64 và trình biên dịch tình cờ chọn asm đó. (Như câu trả lời của @ icarus cho thấy).
Xem lập trình MCU - Tối ưu hóa C ++ O2 trong khi lặp - trình xử lý ngắt trên vi điều khiển lõi đơn về cơ bản giống như trình xử lý tín hiệu trong một chương trình luồng đơn. Trong trường hợp đó, kết quả của UB là một tải đã được kéo ra khỏi một vòng lặp.
Trường hợp thử nghiệm xé rách của bạn thực sự xảy ra do cuộc đua dữ liệu UB có thể đã được phát triển / thử nghiệm ở chế độ 32 bit hoặc với trình biên dịch giả cũ hơn đã tải riêng các thành viên cấu trúc.
Trong trường hợp của bạn, trình biên dịch có thể tối ưu hóa các cửa hàng từ vòng lặp vô hạn vì không có chương trình không có UB nào có thể quan sát chúng. data
là không _Atomic
hoặcvolatile
, và không có tác dụng phụ nào khác trong vòng lặp. Vì vậy, không có cách nào bất kỳ độc giả có thể đồng bộ hóa với nhà văn này. Thực tế điều này xảy ra nếu bạn biên dịch với kích hoạt tối ưu hóa ( Godbolt hiển thị một vòng lặp trống ở dưới cùng của chính). Tôi cũng đã thay đổi struct thành hai long long
và gcc sử dụng một movdqa
lưu trữ 16 byte duy nhất trước vòng lặp. (Điều này không đảm bảo nguyên tử, nhưng nó là trong thực tế trên hầu hết các CPU, giả sử nó thẳng hàng, hoặc trên Intel chỉ đơn thuần là không vượt qua một ranh giới bộ nhớ cache-line. Tại sao là số nguyên phân trên một cách tự nhiên liên kết biến nguyên tử trên x86? )
Vì vậy, biên dịch với tối ưu hóa được kích hoạt cũng sẽ phá vỡ thử nghiệm của bạn và hiển thị cho bạn cùng một giá trị mỗi lần. C không phải là ngôn ngữ lắp ráp di động.
volatile struct two_int
cũng sẽ buộc trình biên dịch không tối ưu hóa chúng đi, nhưng sẽ không buộc nó tải / lưu trữ toàn bộ cấu trúc nguyên tử. (Tuy nhiên, điều đó cũng không ngăn được họ làm như vậy.) Lưu ý rằng điều volatile
đó không tránh được cuộc đua dữ liệu UB, nhưng trên thực tế, nó đủ để giao tiếp giữa các luồng và là cách mọi người chế tạo các nguyên tử cuộn bằng tay (cùng với asm nội tuyến) trước C11 / C ++ 11, đối với kiến trúc CPU bình thường. Họ đang nhớ cache-mạch lạc như vậy volatile
là trong thực tế hầu như tương tự như _Atomic
vớimemory_order_relaxed
cho tinh khiết-load và tinh khiết-store, nếu được sử dụng với nhiều loại thu hẹp đủ để trình biên dịch sẽ sử dụng một chỉ dẫn duy nhất, do đó bạn không nhận được xé. Và dĩ nhiênvolatile
không có bất kỳ sự đảm bảo nào từ tiêu chuẩn ISO C so với mã viết biên dịch cho cùng một asm sử dụng _Atomic
và mo_relaxed.
Nếu bạn có một chức năng đã thực hiện global_var++;
trên int
hoặc long long
bạn chạy từ chính và không đồng bộ từ bộ xử lý tín hiệu, đó sẽ là một cách để sử dụng quyền truy cập lại để tạo UB chạy dữ liệu.
Tùy thuộc vào cách nó được biên dịch (đến đích bộ nhớ inc hoặc thêm hoặc tách riêng tải / inc / store), nó sẽ là nguyên tử hoặc không liên quan đến trình xử lý tín hiệu trong cùng một luồng. Xem num num ++ có thể là nguyên tử cho 'int num' không? để biết thêm về tính nguyên tử trên x86 và trong C ++. ( Thuộc tính stdatomic.h
và _Atomic
thuộc tính của C11 cung cấp chức năng tương đương với std::atomic<T>
mẫu của C ++ 11 )
Một ngắt hoặc ngoại lệ khác không thể xảy ra ở giữa một lệnh, vì vậy, phần bổ sung đích bộ nhớ là wrt nguyên tử. bối cảnh chuyển đổi trên CPU lõi đơn. Chỉ có một trình ghi DMA (bộ nhớ đệm) có thể "bước lên" một mức tăng từ add [mem], 1
không có lock
tiền tố trên CPU lõi đơn. Không có lõi nào khác mà luồng khác có thể chạy.
Vì vậy, nó tương tự như trường hợp tín hiệu: bộ xử lý tín hiệu chạy thay vì thực thi thông thường của luồng xử lý tín hiệu, do đó, nó không thể được xử lý ở giữa một lệnh.