Làm cách nào để tạo một vòng lặp trống vô hạn không được tối ưu hóa?


131

Tiêu chuẩn C11 dường như ngụ ý rằng các câu lệnh lặp với các biểu thức kiểm soát không đổi không nên được tối ưu hóa. Tôi đang lấy lời khuyên của tôi từ câu trả lời này , trong đó trích dẫn cụ thể phần 6.8.5 từ tiêu chuẩn dự thảo:

Một câu lệnh lặp có biểu thức kiểm soát không phải là biểu thức hằng ... có thể được giả định bằng cách thực hiện để chấm dứt.

Trong câu trả lời đó, nó đề cập rằng một vòng lặp như while(1) ;không nên được tối ưu hóa.

Vậy ... tại sao Clang / LLVM tối ưu hóa vòng lặp bên dưới (được biên dịch với cc -O2 -std=c11 test.c -o test)?

#include <stdio.h>

static void die() {
    while(1)
        ;
}

int main() {
    printf("begin\n");
    die();
    printf("unreachable\n");
}

Trên máy của tôi, cái này in ra begin, sau đó đâm vào một lệnh bất hợp pháp (một ud2cái bẫy được đặt sau die()). Trên godbolt , chúng ta có thể thấy rằng không có gì được tạo ra sau cuộc gọi đến puts.

Đây là một nhiệm vụ khó khăn đáng ngạc nhiên để khiến Clang tạo ra một vòng lặp vô hạn bên dưới -O2- trong khi tôi có thể liên tục kiểm tra một volatilebiến, liên quan đến việc đọc bộ nhớ mà tôi không muốn. Và nếu tôi làm một cái gì đó như thế này:

#include <stdio.h>

static void die() {
    while(1)
        ;
}

int main() {
    printf("begin\n");
    volatile int x = 1;
    if(x)
        die();
    printf("unreachable\n");
}

... Clang in begintheo sau unreachablenhư thể vòng lặp vô hạn không bao giờ tồn tại.

Làm thế nào để bạn có được Clang để tạo ra một vòng lặp vô hạn thích hợp, không có bộ nhớ truy cập với các tối ưu hóa được bật?


3
Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Bhargav Rao

2
Không có giải pháp di động nào không liên quan đến tác dụng phụ. Nếu bạn không muốn truy cập bộ nhớ, hy vọng tốt nhất của bạn sẽ được đăng ký char không dấu dễ bay hơi; nhưng đăng ký biến mất trong C ++ 17.
Scott M

25
Có thể điều này không nằm trong phạm vi của câu hỏi, nhưng tôi tò mò tại sao bạn muốn làm điều này. Chắc chắn có một số cách khác để hoàn thành nhiệm vụ thực sự của bạn. Hay đây chỉ là học thuật trong tự nhiên?
Cruncher

1
@Cruncher: Tác động của bất kỳ nỗ lực cụ thể nào để chạy chương trình có thể hữu ích, về cơ bản là vô dụng hoặc tồi tệ hơn là vô dụng. Một thực thi dẫn đến một chương trình bị kẹt trong một vòng lặp vô tận có thể là vô ích, nhưng vẫn thích hợp hơn với các hành vi khác mà trình biên dịch có thể thay thế.
supercat

6
@Cruncher: Bởi vì mã có thể đang chạy trong một bối cảnh tự do, nơi không có khái niệm về exit(), và bởi vì mã có thể đã phát hiện ra một tình huống mà nó không thể đảm bảo rằng các hiệu ứng của việc thực thi tiếp tục sẽ không tệ hơn vô dụng . Vòng lặp tự nhảy là một cách khá tệ hại để xử lý các tình huống như vậy, nhưng dù sao nó cũng có thể là cách tốt nhất để xử lý một tình huống xấu.
supercat

Câu trả lời:


77

Tiêu chuẩn C11 cho biết điều này, 6.8.5 / 6:

Câu lệnh lặp có biểu thức điều khiển không phải là biểu thức không đổi, 156) không thực hiện các hoạt động đầu vào / đầu ra, không truy cập các đối tượng dễ bay hơi và không thực hiện các hoạt động đồng bộ hóa hoặc nguyên tử trong cơ thể, biểu thức điều khiển hoặc (trong trường hợp của tuyên bố) biểu thức của nó-3, có thể được giả định bằng cách thực hiện để chấm dứt. 157)

Hai lưu ý chân không quy phạm nhưng cung cấp thông tin hữu ích:

156) Biểu thức điều khiển bị bỏ qua được thay thế bằng hằng số khác không, là biểu thức hằng.

157) Điều này nhằm cho phép các phép biến đổi trình biên dịch như loại bỏ các vòng lặp trống ngay cả khi việc chấm dứt không thể được chứng minh.

Trong trường hợp của bạn, while(1)là một biểu thức hằng số rõ ràng, vì vậy nó có thể không được giả định bằng cách thực hiện để chấm dứt. Việc triển khai như vậy sẽ bị phá vỡ một cách vô vọng, vì các vòng lặp "mãi mãi" là một cấu trúc lập trình phổ biến.

Tuy nhiên, điều gì xảy ra với "mã không thể truy cập" sau vòng lặp, theo như tôi biết, không được xác định rõ. Tuy nhiên, clang thực sự cư xử rất kỳ lạ. So sánh mã máy với gcc (x86):

gcc 9,2 -O3 -std=c11 -pedantic-errors

.LC0:
        .string "begin"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
.L2:
        jmp     .L2

tiếng kêu 9.0.0 -O3 -std=c11 -pedantic-errors

main:                                   # @main
        push    rax
        mov     edi, offset .Lstr
        call    puts
.Lstr:
        .asciz  "begin"

gcc tạo vòng lặp, tiếng kêu chỉ chạy vào rừng và thoát ra với lỗi 255.

Tôi đang nghiêng về phía hành vi không tuân thủ tiếng kêu này. Bởi vì tôi đã cố gắng mở rộng ví dụ của bạn như thế này:

#include <stdio.h>
#include <setjmp.h>

static _Noreturn void die() {
    while(1)
        ;
}

int main(void) {
    jmp_buf buf;
    _Bool first = !setjmp(buf);

    printf("begin\n");
    if(first)
    {
      die();
      longjmp(buf, 1);
    }
    printf("unreachable\n");
}

Tôi đã thêm C11 _Noreturntrong một nỗ lực để giúp trình biên dịch đi xa hơn. Cần phải rõ ràng rằng chức năng này sẽ bị treo, từ từ khóa đó.

setjmpsẽ trả về 0 khi thực hiện lần đầu tiên, vì vậy chương trình này chỉ cần đập vào while(1)và dừng ở đó, chỉ in "bắt đầu" (giả sử \ n tuôn ra thiết bị xuất chuẩn). Điều này xảy ra với gcc.

Nếu vòng lặp được gỡ bỏ đơn giản, nó sẽ in "bắt đầu" 2 lần sau đó in "không thể truy cập". Tuy nhiên, trên clang ( godbolt ), nó in "bắt đầu" 1 lần và sau đó "không thể truy cập" trước khi trả lại mã thoát 0. Điều đó hoàn toàn sai cho dù bạn đặt nó như thế nào.

Tôi không thể tìm thấy trường hợp nào để tuyên bố hành vi không xác định ở đây, vì vậy tôi cho rằng đây là một lỗi trong tiếng kêu. Ở mức độ nào, hành vi này làm cho tiếng kêu vô dụng 100% đối với các chương trình như hệ thống nhúng, trong đó bạn chỉ cần có thể dựa vào các vòng lặp vĩnh cửu treo chương trình (trong khi chờ đợi cơ quan giám sát, v.v.).


15
Tôi không đồng ý với "đây là một biểu thức hằng số rõ ràng, vì vậy nó có thể không được giả định bằng cách thực hiện để chấm dứt" . Điều này thực sự đi vào việc lập luật ngôn ngữ kén chọn, nhưng 6.8.5/6ở dạng if (những) thì bạn có thể giả định (điều này) . Điều đó không có nghĩa là nếu không (những) bạn không thể giả định (điều này) . Đó là một đặc điểm kỹ thuật chỉ khi các điều kiện được đáp ứng, không phải khi chúng không được đáp ứng, nơi bạn có thể làm bất cứ điều gì bạn muốn với các tiêu chuẩn. Và nếu không có vật quan sát ...
kabanus

7
@kabanus Phần trích dẫn là một trường hợp đặc biệt. Nếu không (trường hợp đặc biệt), hãy đánh giá và sắp xếp mã như bình thường. Nếu bạn tiếp tục đọc cùng một chương, biểu thức kiểm soát được đánh giá như được chỉ định cho từng câu lệnh lặp ("như được chỉ định bởi ngữ nghĩa") ngoại trừ trường hợp đặc biệt được trích dẫn. Nó tuân theo các quy tắc tương tự như đánh giá của bất kỳ tính toán giá trị nào, được sắp xếp theo trình tự và được xác định rõ.
Lundin

2
Tôi đồng ý, nhưng bạn sẽ không ngạc nhiên rằng trong int z=3; int y=2; int x=1; printf("%d %d\n", x, z);đó không có 2trong hội đồng, vì vậy trong ý nghĩa vô dụng trống rỗng xkhông được chỉ định sau ymà sau khi ztối ưu hóa. Vì vậy, đi từ câu cuối cùng của bạn, chúng tôi tuân theo các quy tắc thông thường, giả sử trong khi tạm dừng (vì chúng tôi không bị hạn chế nào tốt hơn) và để lại trong bản in "không thể truy cập" cuối cùng. Bây giờ, chúng tôi tối ưu hóa tuyên bố vô dụng đó (vì chúng tôi không biết gì hơn).
kabanus

2
@MSalters Một trong những bình luận của tôi đã bị xóa, nhưng cảm ơn về đầu vào - và tôi đồng ý. Những gì bình luận của tôi nói là tôi nghĩ đây là trọng tâm của cuộc tranh luận - while(1);giống như một int y = 2;tuyên bố về mặt ngữ nghĩa mà chúng ta được phép tối ưu hóa, ngay cả khi logic của chúng vẫn còn trong nguồn. Từ n1528 tôi đã có ấn tượng rằng họ có thể giống nhau, nhưng vì những người có kinh nghiệm hơn tôi đang tranh luận theo cách khác, và đó là một lỗi chính thức rõ ràng, sau đó vượt ra ngoài một cuộc tranh luận triết học về việc từ ngữ trong tiêu chuẩn có rõ ràng không , đối số được hiển thị moot.
kabanus

2
Một cách thực hiện như vậy sẽ bị phá vỡ một cách vô vọng, vì các vòng lặp 'mãi mãi' là một cấu trúc lập trình phổ biến. - Tôi hiểu tình cảm nhưng đối số còn thiếu sót vì nó có thể được áp dụng giống hệt cho C ++, tuy nhiên trình biên dịch C ++ đã tối ưu hóa vòng lặp này sẽ không bị phá vỡ mà tuân thủ.
Konrad Rudolph

52

Bạn cần chèn một biểu thức có thể gây ra tác dụng phụ.

Giải pháp đơn giản nhất:

static void die() {
    while(1)
       __asm("");
}

Liên kết Godbolt


21
Tuy nhiên, không giải thích tại sao clang đang hoạt động.
Lundin

4
Chỉ cần nói "đó là một lỗi trong tiếng kêu" là đủ. Tôi muốn thử một vài điều ở đây trước tiên, trước khi tôi hét lên "lỗi".
Lundin

3
@Lundin Mình không biết có phải lỗi không. Tiêu chuẩn không chính xác về mặt kỹ thuật trong trường hợp này
P__J__

4
May mắn thay, GCC là mã nguồn mở và tôi có thể viết một trình biên dịch tối ưu hóa ví dụ của bạn. Và tôi có thể làm như vậy cho bất kỳ ví dụ nào bạn nghĩ ra, bây giờ và trong tương lai.
Thomas Weller

3
@ThomasWeller: Nhà phát triển GCC sẽ không chấp nhận bản vá tối ưu hóa vòng lặp này; nó sẽ vi phạm tài liệu = hành vi được đảm bảo. Xem nhận xét trước đây của tôi: asm("")là ngầm định asm volatile("");và do đó, câu lệnh asm phải chạy nhiều lần như trong máy trừu tượng gcc.gnu.org/onlinesocs/gcc/Basic-Asm.html . (Lưu ý rằng các tác dụng phụ của nó không an toàn khi bao gồm bất kỳ bộ nhớ hoặc thanh ghi nào; bạn cần Extended asm với "memory"clobber nếu bạn muốn đọc hoặc ghi bộ nhớ mà bạn từng truy cập từ C. Basic asm chỉ an toàn cho những thứ như asm("mfence")hoặc cli.)
Peter Cordes

50

Các câu trả lời khác đã được đề cập đến các cách để làm cho Clang phát ra vòng lặp vô hạn, với ngôn ngữ lắp ráp nội tuyến hoặc các tác dụng phụ khác. Tôi chỉ muốn xác nhận rằng đây thực sự là một lỗi biên dịch. Cụ thể, đó là một lỗi LLVM lâu đời - nó áp dụng khái niệm C ++ về "tất cả các vòng lặp không có tác dụng phụ phải chấm dứt" đối với các ngôn ngữ không nên sử dụng, chẳng hạn như C.

Ví dụ, ngôn ngữ lập trình Rust cũng cho phép các vòng lặp vô hạn và sử dụng LLVM làm phụ trợ và nó cũng có vấn đề tương tự.

Trong ngắn hạn, có vẻ như LLVM sẽ tiếp tục giả định rằng "tất cả các vòng lặp không có tác dụng phụ phải chấm dứt". Đối với bất kỳ ngôn ngữ nào cho phép các vòng lặp vô hạn, LLVM mong muốn giao diện người dùng chèn các llvm.sideeffectopcodes vào các vòng lặp như vậy. Đây là những gì Rust dự định làm, vì vậy Clang (khi biên dịch mã C) có lẽ cũng sẽ phải làm điều đó.


5
Không có gì giống mùi của một lỗi cũ hơn một thập kỷ ... với nhiều bản sửa lỗi và bản vá được đề xuất ... nhưng vẫn chưa được sửa.
Ian Kemp

4
@IanKemp: Để họ sửa lỗi bây giờ sẽ yêu cầu phải thừa nhận rằng họ đã mất mười năm để sửa lỗi. Tốt hơn là giữ hy vọng rằng Tiêu chuẩn sẽ thay đổi để biện minh cho hành vi của họ. Tất nhiên, ngay cả khi tiêu chuẩn đã thay đổi, điều đó vẫn sẽ không biện minh cho hành vi của họ ngoại trừ trong mắt những người coi thay đổi đối với Tiêu chuẩn là một dấu hiệu cho thấy nhiệm vụ hành vi trước đó của Tiêu chuẩn là một khiếm khuyết cần được sửa chữa hồi tố.
supercat

4
Nó đã được "sửa" theo nghĩa là LLVM đã thêm sideeffectop (vào năm 2017) và hy vọng các front-end sẽ chèn op đó vào các vòng theo ý của họ. LLVM đã phải chọn một số mặc định cho các vòng lặp và tình cờ chọn một vòng lặp phù hợp với hành vi của C ++, cố ý hay cách khác. Tất nhiên, vẫn còn một số công việc tối ưu hóa còn phải hoàn thành, chẳng hạn như hợp nhất các sideeffectop liên tiếp thành một. (Đây là thứ ngăn chặn giao diện người dùng Rust sử dụng nó.) Vì vậy, trên cơ sở đó, lỗi nằm ở mặt trước (tiếng kêu) không chèn op vào vòng lặp.
Arnavion

@Arnavion: Có cách nào để chỉ ra rằng các hoạt động có thể bị hoãn lại trừ khi hoặc cho đến khi kết quả được sử dụng, nhưng nếu dữ liệu sẽ khiến chương trình lặp lại vô tận, cố gắng tiến hành phụ thuộc dữ liệu trong quá khứ sẽ khiến chương trình trở nên tồi tệ hơn ? Việc phải thêm các hiệu ứng phụ giả mạo sẽ ngăn chặn các tối ưu hóa hữu ích trước đây để ngăn chặn trình tối ưu hóa làm cho chương trình trở nên tồi tệ hơn vô dụng không giống như một công thức cho hiệu quả.
supercat

Cuộc thảo luận đó có lẽ thuộc về danh sách gửi thư LLVM / clang. FWIW cam kết LLVM đã thêm op cũng đã dạy một số cách tối ưu hóa về nó. Ngoài ra, Rust đã thử nghiệm với việc chèn sideeffectop vào đầu mọi chức năng và không thấy bất kỳ hồi quy hiệu năng thời gian chạy nào. Vấn đề duy nhất là hồi quy thời gian biên dịch , rõ ràng là do thiếu sự hợp nhất của các op liên tiếp như tôi đã đề cập trong bình luận trước đây của tôi.
Arnavion

32

Đây là một lỗi Clang

... khi nội tuyến một hàm chứa một vòng lặp vô hạn. Hành vi là khác nhau khi while(1);xuất hiện trực tiếp trong chính, có mùi rất lỗi với tôi.

Xem câu trả lời của @ Arnavion để biết tóm tắt và liên kết. Phần còn lại của câu trả lời này được viết trước khi tôi xác nhận rằng đó là một lỗi, chứ chưa nói đến một lỗi đã biết.


Để trả lời câu hỏi tiêu đề: Làm cách nào để tạo một vòng lặp vô hạn không được tối ưu hóa? ? -
tạo die()một macro, không phải là một hàm , để khắc phục lỗi này trong Clang 3.9 trở lên. (Phiên bản Clang Đầu hoặc giữ cho vòng lặp hoặc phát ra mộtcall đến một phiên bản không-inline của hàm với các vòng lặp vô hạn.) Đó dường như là an toàn ngay cả khi print;while(1);print;inlines chức năng vào gọi ( Godbolt ). -std=gnu11so với -std=gnu99không thay đổi bất cứ điều gì.

Nếu bạn chỉ quan tâm đến GNU C, thì P__J ____asm__(""); bên trong vòng lặp cũng hoạt động và không nên làm tối ưu hóa bất kỳ mã xung quanh nào cho bất kỳ trình biên dịch nào hiểu nó. Các câu lệnh asm GNU C Basic được ngầm địnhvolatile , do đó, điều này được tính là một hiệu ứng phụ có thể nhìn thấy phải "thực thi" nhiều lần như trong máy trừu tượng C. (Và vâng, Clang thực hiện phương ngữ GNU của C, như được hướng dẫn bởi tài liệu hướng dẫn GCC.)


Một số người đã lập luận rằng nó có thể là hợp pháp để tối ưu hóa một vòng lặp vô hạn trống. Tôi không đồng ý 1 , nhưng ngay cả khi chúng tôi chấp nhận điều đó, Clang cũng không thể hợp pháp để giả sử các câu lệnh sau khi vòng lặp không thể truy cập được và để cho việc thực thi rơi vào phần cuối của hàm vào hàm tiếp theo hoặc vào thùng rác mà giải mã như hướng dẫn ngẫu nhiên.

(Điều đó sẽ tuân thủ tiêu chuẩn cho Clang ++ (nhưng vẫn không hữu ích lắm); các vòng lặp vô hạn không có bất kỳ tác dụng phụ nào là UB trong C ++, nhưng không phải C.
Trong khi (1); hành vi không xác định trong C? UB cho phép trình biên dịch phát ra bất cứ thứ gì về cơ bản đối với mã trên đường dẫn thực thi chắc chắn sẽ gặp UB. Một asmcâu lệnh trong vòng lặp sẽ tránh UB này cho C ++. Nhưng trong thực tế, Clang biên dịch như C ++ không loại bỏ các vòng lặp vô hạn biểu thức không đổi trừ khi nội tuyến, giống như khi biên dịch thành C.)


Thủ công nội tuyến while(1);thay đổi cách Clang biên dịch nó: vòng lặp vô hạn hiện diện trong asm. Đây là những gì chúng ta mong đợi từ một luật sư POV.

#include <stdio.h>
int main() {
    printf("begin\n");
    while(1);
    //infloop_nonconst(1);
    //infloop();
    printf("unreachable\n");
}

Trên trình thám hiểm trình biên dịch Godbolt , Clang 9.0 -O3 biên dịch thành C ( -xc) cho x86-64:

main:                                   # @main
        push    rax                       # re-align the stack by 16
        mov     edi, offset .Lstr         # non-PIE executable can use 32-bit absolute addresses
        call    puts
.LBB3_1:                                # =>This Inner Loop Header: Depth=1
        jmp     .LBB3_1                   # infinite loop


.section .rodata
 ...
.Lstr:
        .asciz  "begin"

Trình biên dịch tương tự với cùng các tùy chọn sẽ biên dịch một mainlệnh gọi infloop() { while(1); }đến cùng trước puts, nhưng sau đó chỉ dừng phát ra các hướng dẫn cho mainsau thời điểm đó. Vì vậy, như tôi đã nói, việc thực thi chỉ rơi vào cuối hàm, vào bất kỳ chức năng nào tiếp theo (nhưng với ngăn xếp được sắp xếp sai cho mục nhập chức năng để nó thậm chí không phải là một đuôi hợp lệ).

Các tùy chọn hợp lệ sẽ là

  • phát ra một label: jmp labelvòng lặp vô hạn
  • hoặc (nếu chúng tôi chấp nhận rằng vòng lặp vô hạn có thể được loại bỏ) phát ra một cuộc gọi khác để in chuỗi thứ 2, sau đó return 0từ main.

Sự cố hoặc tiếp tục mà không in "không thể truy cập" rõ ràng là không ổn đối với việc triển khai C11, trừ khi có UB mà tôi không nhận thấy.


Chú thích 1:

Đối với hồ sơ, tôi đồng ý với câu trả lời của @ Lundin, trích dẫn tiêu chuẩn cho bằng chứng rằng C11 không cho phép giả định chấm dứt đối với các vòng lặp vô hạn biểu thức không đổi, ngay cả khi chúng trống (không có I / O, dễ bay hơi, đồng bộ hóa hoặc khác tác dụng phụ có thể nhìn thấy).

Đây là tập hợp các điều kiện sẽ cho phép một vòng lặp được biên dịch thành một vòng lặp asm trống cho một CPU bình thường. (Ngay cả khi phần thân không trống trong nguồn, các phép gán cho các biến không thể hiển thị cho các luồng hoặc trình xử lý tín hiệu khác mà không có cuộc đua dữ liệu UB trong khi vòng lặp đang chạy. Vì vậy, việc triển khai tuân thủ có thể loại bỏ các thân vòng lặp đó nếu muốn đến. Sau đó, câu hỏi đặt ra là liệu vòng lặp có thể được gỡ bỏ hay không. ISO C11 nói rõ ràng là không.)

Cho rằng C11 chỉ ra trường hợp đó như là một trường hợp trong đó việc triển khai không thể giả sử vòng lặp chấm dứt (và đó không phải là UB), có vẻ như rõ ràng họ dự định vòng lặp sẽ có mặt vào thời gian chạy. Việc triển khai nhắm mục tiêu CPU với mô hình thực thi không thể thực hiện một lượng công việc vô hạn trong thời gian hữu hạn không có lý do gì để loại bỏ một vòng lặp vô hạn liên tục trống. Hoặc thậm chí nói chung, từ ngữ chính xác là về việc họ có thể "giả định chấm dứt" hay không. Nếu một vòng lặp không thể chấm dứt, điều đó có nghĩa là mã sau này không thể truy cập được, bất kể bạn tranh luận gì về toán học và vô số và mất bao lâu để thực hiện một lượng công việc vô hạn trên một số máy giả định.

Ngoài ra, Clang không chỉ đơn thuần là một DeathStation 9000 tuân thủ ISO C, nó dự định sẽ hữu ích cho lập trình hệ thống cấp thấp trong thế giới thực, bao gồm cả nhân và các công cụ nhúng. Vì vậy, cho dù bạn có chấp nhận tranh luận về việc C11 cho phép loại bỏ hay không while(1);, điều đó không có nghĩa là Clang sẽ thực sự muốn làm điều đó. Nếu bạn viết while(1);, đó có lẽ không phải là một tai nạn. Việc loại bỏ các vòng lặp kết thúc vô hạn một cách tình cờ (với các biểu thức điều khiển biến thời gian chạy) có thể hữu ích và thật hợp lý khi trình biên dịch thực hiện điều đó.

Thật hiếm khi bạn muốn quay cho đến lần gián đoạn tiếp theo, nhưng nếu bạn viết điều đó trong C thì đó chắc chắn là điều bạn mong đợi sẽ xảy ra. (Và những gì không xảy ra trong GCC và Clang, trừ Clang khi vòng lặp vô hạn là bên trong một hàm wrapper).

Ví dụ, trong nhân hệ điều hành nguyên thủy, khi bộ lập lịch không có tác vụ để chạy, nó có thể chạy tác vụ nhàn rỗi. Việc thực hiện đầu tiên có thể là while(1);.

Hoặc đối với phần cứng không có bất kỳ tính năng nhàn rỗi tiết kiệm năng lượng nào, đó có thể là triển khai duy nhất. (Cho đến đầu những năm 2000, đó là điều tôi nghĩ không hiếm trên x86. Mặc dù hlthướng dẫn đã tồn tại, IDK nếu nó tiết kiệm được một lượng điện năng có ý nghĩa cho đến khi CPU bắt đầu có trạng thái nhàn rỗi năng lượng thấp.)


1
Vì tò mò, có ai thực sự sử dụng tiếng kêu cho các hệ thống nhúng không? Tôi chưa bao giờ nhìn thấy nó và tôi làm việc độc quyền với nhúng. gcc chỉ "gần đây" (10 năm trước) tham gia vào thị trường nhúng và tôi sử dụng nó một cách có ý thức, tốt nhất là với tối ưu hóa thấp và luôn luôn với -ffreestanding -fno-strict-aliasing. Nó hoạt động tốt với ARM và có lẽ với AVR kế thừa.
Lundin

1
@Lundin: IDK về nhúng, nhưng vâng, mọi người thường xây dựng hạt nhân bằng tiếng kêu, ít nhất là đôi khi là Linux. Có lẽ cũng Darwin cho MacOS.
Peter Cordes

2
bug.llvm.org/show_orms.cgi?id=965 lỗi này có vẻ phù hợp, nhưng tôi không chắc đó là những gì chúng ta đang thấy ở đây.
bracco23

1
@lundin - Tôi khá chắc chắn rằng chúng tôi đã sử dụng GCC (và rất nhiều bộ công cụ khác) cho công việc nhúng trong suốt thập niên 90, với RTOS như VxWorks và PSOS. Tôi không hiểu tại sao bạn nói GCC chỉ tham gia vào thị trường nhúng gần đây.
Jeff Learman

1
@JeffLearman Trở thành chủ đạo gần đây, sau đó? Dù sao, fiasco nghiêm ngặt gcc chỉ xảy ra sau khi giới thiệu C99, và các phiên bản mới hơn của nó dường như không còn chuối khi gặp phải vi phạm bí danh nghiêm ngặt. Tuy nhiên, tôi vẫn hoài nghi bất cứ khi nào tôi sử dụng nó. Đối với tiếng kêu, phiên bản mới nhất rõ ràng đã bị hỏng hoàn toàn khi nói đến các vòng lặp vĩnh cửu, vì vậy nó không thể được sử dụng cho các hệ thống nhúng.
Lundin

14

Chỉ để ghi lại, Clang cũng hành vi sai với goto:

static void die() {
nasty:
    goto nasty;
}

int main() {
    int x; printf("begin\n");
    die();
    printf("unreachable\n");
}

Nó tạo ra đầu ra giống như trong câu hỏi, nghĩa là:

main: # @main
  push rax
  mov edi, offset .Lstr
  call puts
.Lstr:
  .asciz "begin"

Tôi thấy không thấy cách nào để đọc cái này như được cho phép trong C11, mà chỉ nói:

6.8.6.1 (2) Một gotocâu lệnh gây ra một bước nhảy vô điều kiện đến câu lệnh được tiền tố bởi nhãn có tên trong hàm kèm theo.

Như gotokhông phải là một "tuyên bố lặp" (6.8.5 danh sách while, dofor) gì về đặc biệt "chấm dứt-giả định" ân xá áp dụng, tuy nhiên bạn muốn đọc chúng.

Trình biên dịch liên kết Godbolt của mỗi câu hỏi ban đầu là x86-64 Clang 9.0.0 và các cờ là -g -o output.s -mllvm --x86-asm-syntax=intel -S --gcc-toolchain=/opt/compiler-explorer/gcc-9.2.0 -fcolor-diagnostics -fno-crash-diagnostics -O2 -std=c11 example.c

Với những người khác như x86-64 GCC 9.2, bạn sẽ có được sự hoàn hảo khá tốt:

.LC0:
  .string "begin"
main:
  sub rsp, 8
  mov edi, OFFSET FLAT:.LC0
  call puts
.L2:
  jmp .L2

Cờ: -g -o output.s -masm=intel -S -fdiagnostics-color=always -O2 -std=c11 example.c


Việc triển khai tuân thủ có thể có giới hạn dịch không có giấy tờ về thời gian thực hiện hoặc chu kỳ CPU có thể gây ra hành vi tùy ý nếu vượt quá hoặc nếu một chương trình đầu vào vượt quá giới hạn không thể tránh khỏi. Những thứ như vậy là vấn đề Chất lượng Thực hiện, nằm ngoài phạm vi quyền hạn của Tiêu chuẩn. Có vẻ kỳ lạ là những người duy trì tiếng kêu sẽ khăng khăng đòi quyền của họ để tạo ra một triển khai chất lượng kém, nhưng Tiêu chuẩn cho phép điều đó.
supercat

2
@supercat cảm ơn vì đã bình luận ... tại sao vượt quá giới hạn dịch sẽ làm bất cứ điều gì khác ngoài việc không thực hiện giai đoạn dịch và từ chối thực hiện? Ngoài ra: " 5.1.1.3 Chẩn đoán Việc triển khai tuân thủ sẽ tạo ra ... thông báo chẩn đoán ... nếu đơn vị dịch hoặc đơn vị dịch tiền xử lý có vi phạm bất kỳ quy tắc cú pháp hoặc ràng buộc nào ...". Tôi không thể thấy hành vi sai lầm ở giai đoạn thực thi có thể tuân thủ như thế nào.
jonathanjo

Tiêu chuẩn sẽ hoàn toàn không thể thực hiện nếu tất cả các giới hạn thực hiện phải được giải quyết tại thời điểm xây dựng, vì người ta có thể viết chương trình Tuân thủ nghiêm ngặt sẽ yêu cầu nhiều byte hơn so với các nguyên tử trong vũ trụ. Không rõ liệu các giới hạn thời gian chạy có nên được gộp lại với "giới hạn dịch" hay không, nhưng một sự nhượng bộ như vậy rõ ràng là cần thiết và không có thể loại nào khác được đưa vào.
supercat

1
Tôi đã trả lời bình luận của bạn về "giới hạn dịch thuật". Tất nhiên cũng có giới hạn thực thi, tôi thú nhận tôi không hiểu tại sao bạn đề nghị họ nên gộp các giới hạn dịch thuật hoặc tại sao bạn nói điều đó là cần thiết. Tôi chỉ không thấy bất kỳ lý do nào để nói nasty: goto nastycó thể tuân thủ và không quay (các) CPU cho đến khi người dùng hoặc tài nguyên cạn kiệt can thiệp.
jonathanjo

1
Tiêu chuẩn không tham chiếu đến "giới hạn thực hiện" mà tôi có thể tìm thấy. Những điều như chức năng cuộc gọi làm tổ thường được xử lý bằng cách phân bổ chồng, nhưng một thực hiện phù hợp mà giới hạn chức năng cuộc gọi đến độ sâu 16 có thể xây dựng 16 bản sao của tất cả các chức năng, và có một cuộc gọi đến bar()trong foo()được xử lý như một cuộc gọi từ __1foođến __2bar, từ __2foođến __3bar, vv và từ __16foođến __launch_nasal_demons, sau đó sẽ cho phép tất cả các đối tượng tự động được phân bổ tĩnh, và sẽ làm những gì thường một "thời gian chạy" giới hạn vào một giới hạn dịch.
supercat

5

Tôi sẽ đóng vai người ủng hộ của quỷ và cho rằng tiêu chuẩn không rõ ràng cấm trình biên dịch tối ưu hóa một vòng lặp vô hạn.

Câu lệnh lặp có biểu thức điều khiển không phải là biểu thức không đổi, 156) không thực hiện các hoạt động đầu vào / đầu ra, không truy cập các đối tượng dễ bay hơi và không thực hiện các hoạt động đồng bộ hóa hoặc nguyên tử trong cơ thể, biểu thức điều khiển hoặc (trong trường hợp của tuyên bố) biểu thức của nó-3, có thể được giả định bằng cách thực hiện để chấm dứt.157)

Hãy phân tích cái này. Một tuyên bố lặp lại thỏa mãn các tiêu chí nhất định có thể được giả định để chấm dứt:

if (satisfiesCriteriaForTerminatingEh(a_loop)) 
    if (whatever_reason_or_just_because_you_feel_like_it)
         assumeTerminates(a_loop);

Điều này không nói lên điều gì xảy ra nếu các tiêu chí không thỏa mãn và cho rằng một vòng lặp có thể chấm dứt ngay cả khi đó không bị cấm rõ ràng miễn là các quy tắc khác của tiêu chuẩn được tuân thủ.

do { } while(0)hoặc while(0){}sau tất cả các câu lệnh lặp (vòng lặp) không thỏa mãn các tiêu chí cho phép trình biên dịch chỉ giả định rằng họ sẽ chấm dứt và rõ ràng là chúng chấm dứt.

Nhưng trình biên dịch có thể chỉ tối ưu hóa while(1){}không?

5.1.2.3p4 nói:

Trong máy trừu tượng, tất cả các biểu thức được đánh giá theo quy định của ngữ nghĩa. Việc triển khai thực tế không cần đánh giá một phần của biểu thức nếu nó có thể suy ra rằng giá trị của nó không được sử dụng và không có tác dụng phụ cần thiết nào được tạo ra (bao gồm mọi tác nhân gây ra bằng cách gọi hàm hoặc truy cập một đối tượng dễ bay hơi).

Điều này đề cập đến các biểu thức, không phải các tuyên bố, vì vậy nó không thuyết phục 100%, nhưng chắc chắn nó cho phép các cuộc gọi như:

void loop(void){ loop(); }

int main()
{
    loop();
}

để được bỏ qua. Thật thú vị, clang không bỏ qua nó, và gcc thì không .


"Điều này không nói lên điều gì xảy ra nếu các tiêu chí không thỏa mãn" Nhưng thực tế, 6.8.5.1 Câu lệnh while: "Việc đánh giá biểu thức kiểm soát diễn ra trước mỗi lần thực hiện thân vòng lặp." Đó là nó. Đây là một tính toán giá trị (của một biểu thức không đổi), nó nằm trong quy tắc của máy trừu tượng 5.1.2.3 xác định đánh giá thuật ngữ: " Đánh giá một biểu thức nói chung bao gồm cả tính toán giá trị và bắt đầu tác dụng phụ." Và theo cùng một chương, tất cả các đánh giá như vậy được giải trình tự và đánh giá theo quy định của ngữ nghĩa.
Lundin

1
@Lundin Vậy while(1){}là một chuỗi các 1đánh giá vô hạn đan xen với các {}đánh giá, nhưng ở đâu trong tiêu chuẩn nó nói rằng những đánh giá đó cần phải mất thời gian khác không ? Hành vi gcc hữu ích hơn, tôi đoán, vì bạn không cần các thủ thuật liên quan đến truy cập bộ nhớ hoặc các thủ thuật bên ngoài ngôn ngữ. Nhưng tôi không tin rằng tiêu chuẩn cấm tối ưu hóa này trong tiếng kêu. Nếu thực hiện không thể while(1){}tối ưu hóa là ý định, thì tiêu chuẩn phải rõ ràng về nó và vòng lặp vô hạn phải được liệt kê như một hiệu ứng phụ có thể quan sát được trong 5.1.2.3p2.
PSkocik

1
Tôi nghĩ rằng nó được chỉ định, nếu bạn coi 1điều kiện là một tính toán giá trị. Thời gian thực hiện không quan trọng - điều quan trọng là những gì while(A){} B;có thể không được tối ưu hóa hoàn toàn, không được tối ưu hóa B;và không được sắp xếp lại B; while(A){}. Để trích dẫn cỗ máy trừu tượng C11, tôi nhấn mạnh: "Sự hiện diện của điểm thứ tự giữa việc đánh giá biểu thức A và B ngụ ý rằng mọi tính toán giá trị và tác dụng phụ liên quan đến A được sắp xếp trước mỗi tính toán giá trị và tác dụng phụ liên quan đến B. " Giá trị của Ađược sử dụng rõ ràng (theo vòng lặp).
Lundin

2
+1 Mặc dù đối với tôi có vẻ như "thực thi bị treo vô thời hạn mà không có đầu ra" là "tác dụng phụ" trong bất kỳ định nghĩa nào về "hiệu ứng phụ" có ý nghĩa và hữu ích ngoài tiêu chuẩn trong chân không, điều này giúp giải thích suy nghĩ từ đó có thể có ý nghĩa với một ai đó.
mtraceur

1
Gần "tối ưu hóa một vòng lặp vô hạn" : Không hoàn toàn rõ ràng liệu "nó" đề cập đến tiêu chuẩn hay trình biên dịch - có lẽ là viết lại? Đưa ra "mặc dù có lẽ nên" và không "mặc dù có lẽ không nên" , nhưng đây có lẽ là tiêu chuẩn mà "nó" đề cập đến.
Peter Mortensen

2

Tôi đã bị thuyết phục rằng đây chỉ là một lỗi cũ đơn giản. Tôi để lại các bài kiểm tra của tôi dưới đây và đặc biệt là tài liệu tham khảo cho các cuộc thảo luận trong ủy ban tiêu chuẩn cho một số lý do mà tôi đã có trước đây.


Tôi nghĩ rằng đây là hành vi không xác định (xem phần cuối) và Clang chỉ có một cách thực hiện. GCC thực sự hoạt động như bạn mong đợi, chỉ tối ưu hóa unreachablecâu lệnh in mà rời khỏi vòng lặp. Một số cách Clang đang đưa ra quyết định kỳ quặc khi kết hợp nội tuyến và xác định những gì nó có thể làm với vòng lặp.

Hành vi này rất kỳ lạ - nó loại bỏ bản in cuối cùng, do đó "nhìn thấy" vòng lặp vô hạn, nhưng sau đó cũng thoát khỏi vòng lặp.

Nó thậm chí còn tồi tệ hơn như tôi có thể nói. Loại bỏ nội tuyến chúng tôi nhận được:

die: # @die
.LBB0_1: # =>This Inner Loop Header: Depth=1
  jmp .LBB0_1
main: # @main
  push rax
  mov edi, offset .Lstr
  call puts
.Lstr:
  .asciz "begin"

Vì vậy, chức năng được tạo và cuộc gọi được tối ưu hóa. Điều này thậm chí còn linh hoạt hơn mong đợi:

#include <stdio.h>

void die(int x) {
    while(x);
}

int main() {
    printf("begin\n");
    die(1);
    printf("unreachable\n");
}

dẫn đến một hội đồng rất không tối ưu cho chức năng, nhưng cuộc gọi chức năng lại được tối ưu hóa! Thậm chí tệ hơn:

void die(x) {
    while(x++);
}

int main() {
    printf("begin\n");
    die(1);
    printf("unreachable\n");
}

Tôi đã thực hiện một loạt các thử nghiệm khác với việc thêm một biến cục bộ và tăng nó, chuyển một con trỏ, sử dụng một gotov.v ... Tại thời điểm này tôi sẽ từ bỏ. Nếu bạn phải sử dụng tiếng kêu

static void die() {
    int volatile x = 1;
    while(x);
}

Làm công việc. Nó hút tối ưu hóa (rõ ràng), và rời khỏi trận chung kết dư thừa printf. Ít nhất là chương trình không dừng lại. Có lẽ GCC sau tất cả?

Phụ lục

Sau khi thảo luận với David, tôi cho rằng tiêu chuẩn không nói "nếu điều kiện không đổi, bạn có thể không cho rằng vòng lặp chấm dứt". Như vậy, và được cấp theo tiêu chuẩn, không có hành vi có thể quan sát được (như được định nghĩa trong tiêu chuẩn), tôi sẽ chỉ tranh luận về tính nhất quán - nếu trình biên dịch đang tối ưu hóa một vòng lặp vì cho rằng nó chấm dứt, nó không nên tối ưu hóa các câu lệnh sau.

Heck n1528 có những hành vi không xác định nếu tôi đọc đúng. Đặc biệt

Một vấn đề lớn để làm như vậy là nó cho phép mã di chuyển qua một vòng lặp không có khả năng kết thúc

Từ đây tôi nghĩ rằng nó chỉ có thể chuyển thành một cuộc thảo luận về những gì chúng ta muốn (dự kiến?) Chứ không phải là những gì được cho phép.


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Bhargav Rao

Re "plain all bug" : Ý bạn là " lỗi cũ đơn giản " ?
Peter Mortensen

@PeterMortensen "ole" cũng sẽ ổn với tôi.
kabanus

2

Có vẻ như đây là một lỗi trong trình biên dịch Clang. Nếu không có bất kỳ sự ép buộc nào đối với die()chức năng là một hàm tĩnh, hãy loại bỏ staticvà làm cho nó inline:

#include <stdio.h>

inline void die(void) {
    while(1)
        ;
}

int main(void) {
    printf("begin\n");
    die();
    printf("unreachable\n");
}

Nó hoạt động như mong đợi khi được biên dịch với trình biên dịch Clang và cũng có thể mang theo được.

Trình biên dịch trình duyệt (godbolt.org) - clang 9.0.0-O3 -std=c11 -pedantic-errors

main:                                   # @main
        push    rax
        mov     edi, offset .Lstr
        call    puts
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        jmp     .LBB0_1
.Lstr:
        .asciz  "begin"

Thế còn static inline?
SS Anne

1

Sau đây có vẻ như làm việc cho tôi:

#include <stdio.h>

__attribute__ ((optnone))
static void die(void) {
    while (1) ;
}

int main(void) {
    printf("begin\n");
    die();
    printf("unreachable\n");
}

tại Godbolt

Rõ ràng nói với Clang không tối ưu hóa rằng một chức năng khiến một vòng lặp vô hạn được phát ra như mong đợi. Hy vọng rằng có một cách để vô hiệu hóa có chọn lọc các tối ưu hóa cụ thể thay vì chỉ tắt tất cả chúng như thế. Clang vẫn từ chối phát mã cho lần thứ hai printf, mặc dù. Để buộc nó làm điều đó, tôi đã phải sửa đổi thêm mã bên trong mainthành:

volatile int x = 0;
if (x == 0)
    die();

Có vẻ như bạn sẽ cần phải vô hiệu hóa tối ưu hóa cho chức năng vòng lặp vô hạn của mình, sau đó đảm bảo rằng vòng lặp vô hạn của bạn được gọi là có điều kiện. Trong thế giới thực, dù sao thì hầu như luôn luôn như vậy.


1
Không cần thiết cho cái thứ hai printfđược tạo ra nếu vòng lặp thực sự tồn tại mãi mãi, bởi vì trong trường hợp đó, cái thứ hai printfthực sự không thể truy cập được và do đó có thể bị xóa. (Lỗi của Clang là trong cả việc phát hiện không thể truy cập và sau đó xóa vòng lặp sao cho đạt được mã không thể truy cập được).
nneonneo

Tài liệu GCC __attribute__ ((optimize(1))), nhưng clang bỏ qua nó là không được hỗ trợ: godbolt.org/z/4ba2HM . gcc.gnu.org/onlinesocs/gcc/Common-Function-Attribut.html
Peter Cordes

0

Việc triển khai tuân thủ có thể, và nhiều thực tế thực hiện, áp đặt các giới hạn tùy ý về thời gian chương trình có thể thực thi hoặc bao nhiêu lệnh sẽ thực hiện và hành xử theo cách tùy tiện nếu các giới hạn đó bị vi phạm hoặc - theo quy tắc "như thể nếu" - nếu nó xác định rằng họ chắc chắn sẽ bị vi phạm. Với điều kiện là việc triển khai có thể xử lý thành công ít nhất một chương trình thực hiện tất cả các giới hạn được liệt kê trong N1570 5.2.4.1 mà không đạt bất kỳ giới hạn dịch thuật nào, sự tồn tại của các giới hạn, mức độ mà chúng được ghi lại và các tác động của việc vượt quá chúng tất cả các vấn đề về Chất lượng Thực hiện ngoài phạm vi quyền hạn của Tiêu chuẩn.

Tôi nghĩ rằng ý định của Tiêu chuẩn khá rõ ràng rằng các trình biên dịch không nên cho rằng một while(1) {}vòng lặp không có tác dụng phụ cũng như các breakcâu lệnh sẽ chấm dứt. Trái với những gì một số người có thể nghĩ, các tác giả của Standard không mời các nhà văn biên dịch trở nên ngu ngốc hay khó hiểu. Việc triển khai tuân thủ có thể hữu ích để quyết định chấm dứt bất kỳ chương trình nào, nếu không bị gián đoạn, thực thi các hướng dẫn miễn phí có tác dụng phụ hơn so với các nguyên tử trong vũ trụ, nhưng việc thực hiện chất lượng không nên thực hiện hành động đó trên cơ sở bất kỳ giả định nào về chấm dứt nhưng trên cơ sở rằng làm như vậy có thể hữu ích, và sẽ không (không giống như hành vi của clang) tồi tệ hơn vô dụng.


-2

Vòng lặp không có tác dụng phụ, và do đó có thể được tối ưu hóa. Vòng lặp thực sự là một số lần lặp vô hạn của các đơn vị công việc bằng không. Điều này không được xác định trong toán học và logic và tiêu chuẩn không cho biết việc triển khai có được phép hoàn thành vô số điều hay không nếu mỗi việc có thể được thực hiện trong thời gian 0. Giải thích của Clang là hoàn toàn hợp lý trong việc coi vô số lần bằng 0 thay vì vô hạn. Tiêu chuẩn không cho biết liệu một vòng lặp vô hạn có thể kết thúc hay không nếu tất cả các công việc trong các vòng lặp trên thực tế đã hoàn thành.

Trình biên dịch được phép tối ưu hóa mọi thứ không thể quan sát được như được định nghĩa trong tiêu chuẩn. Điều đó bao gồm thời gian thực hiện. Không bắt buộc phải bảo tồn thực tế rằng vòng lặp, nếu không được tối ưu hóa, sẽ mất một lượng thời gian vô hạn. Nó được phép thay đổi điều đó thành thời gian chạy ngắn hơn nhiều - trên thực tế, đó là điểm của hầu hết các tối ưu hóa. Vòng lặp của bạn đã được tối ưu hóa.

Ngay cả khi clang dịch mã một cách ngây thơ, bạn có thể tưởng tượng một CPU tối ưu hóa có thể hoàn thành mỗi lần lặp trong một nửa thời gian của lần lặp trước đó. Điều đó theo nghĩa đen sẽ hoàn thành vòng lặp vô hạn trong một khoảng thời gian hữu hạn. Liệu một CPU tối ưu hóa như vậy có vi phạm tiêu chuẩn? Có vẻ khá vô lý khi nói rằng một CPU tối ưu hóa sẽ vi phạm tiêu chuẩn nếu quá tối ưu hóa. Điều tương tự cũng đúng với một trình biên dịch.


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Samuel Liew

4
Đánh giá từ kinh nghiệm bạn có (từ hồ sơ của bạn) Tôi chỉ có thể kết luận rằng bài đăng này được viết với mục đích xấu chỉ để bảo vệ trình biên dịch. Bạn đang nghiêm túc lập luận rằng một cái gì đó mất một lượng thời gian vô hạn có thể được tối ưu hóa để thực hiện trong một nửa thời gian. Điều đó là vô lý ở mọi cấp độ và bạn biết điều đó.
đường ống

@pipe: Tôi nghĩ rằng những người duy trì clang và gcc đang hy vọng phiên bản tương lai của Tiêu chuẩn sẽ khiến hành vi của trình biên dịch của họ được cho phép và những người duy trì các trình biên dịch đó sẽ có thể giả vờ rằng sự thay đổi đó chỉ là sự điều chỉnh của một khiếm khuyết lâu dài trong Tiêu chuẩn. Đó là cách họ đã đối xử với các đảm bảo Trình tự ban đầu chung của C89, chẳng hạn.
supercat

@SSAnne: Hmm ... Tôi không nghĩ rằng đủ để chặn một số suy luận không có căn cứ gcc và clang rút ra từ kết quả so sánh bình đẳng con trỏ.
supercat

@supercat Có <s> người khác </ s> tấn.
SS Anne

-2

Tôi xin lỗi nếu điều này không hợp lý, tôi đã tình cờ thấy bài đăng này và tôi biết vì những năm tôi sử dụng bản phân phối Gentoo Linux rằng nếu bạn muốn trình biên dịch không tối ưu hóa mã của mình, bạn nên sử dụng -O0 (Zero). Tôi tò mò về nó, đã biên dịch và chạy đoạn mã trên, và vòng lặp thực hiện vô tận. Được biên dịch bằng clang-9:

cc -O0 -std=c11 test.c -o test

1
Vấn đề là tạo một vòng lặp vô hạn với kích hoạt tối ưu hóa.
SS Anne

-4

Một whilevòng lặp trống không có bất kỳ tác dụng phụ nào trên hệ thống.

Do đó Clang loại bỏ nó. Có những cách "tốt hơn" để đạt được hành vi dự định buộc bạn phải rõ ràng hơn về ý định của mình.

while(1); là baaadd.


6
Trong nhiều cấu trúc nhúng, không có khái niệm về abort()hoặc exit(). Nếu một tình huống phát sinh trong đó một hàm xác định rằng (có lẽ là kết quả của việc hỏng bộ nhớ) thì việc thực thi tiếp tục sẽ tệ hơn nguy hiểm, một hành vi mặc định phổ biến cho các thư viện nhúng là gọi một hàm thực hiện a while(1);. Nó có thể hữu ích cho trình biên dịch có các tùy chọn để thay thế một hành vi hữu ích hơn , nhưng bất kỳ người viết trình biên dịch nào không thể tìm ra cách xử lý một cấu trúc đơn giản như một rào cản để tiếp tục thực hiện chương trình là không đủ khả năng để được tối ưu hóa phức tạp.
supercat

Có cách nào bạn có thể rõ ràng hơn về ý định của bạn? trình tối ưu hóa có mặt để tối ưu hóa chương trình của bạn và loại bỏ các vòng lặp dư thừa mà không làm gì là tối ưu hóa. đây thực sự là một sự khác biệt triết học giữa tư duy trừu tượng của thế giới toán học và thế giới kỹ thuật ứng dụng nhiều hơn.
Jameis nổi tiếng

Hầu hết các chương trình đều có một tập hợp các hành động hữu ích mà họ nên thực hiện khi có thể và một tập hợp các hành động tồi tệ hơn là vô dụng mà họ không bao giờ phải thực hiện trong bất kỳ trường hợp nào. Nhiều chương trình có một tập hợp các hành vi có thể chấp nhận được trong bất kỳ trường hợp cụ thể nào, một trong số đó, nếu không thể quan sát được thời gian thực hiện, sẽ luôn luôn "chờ một số tùy ý và sau đó thực hiện một số hành động từ tập hợp". Nếu tất cả các hành động khác ngoài chờ đợi đều nằm trong tập hợp các hành động tồi tệ hơn vô dụng, sẽ không có số giây N mà "chờ mãi" sẽ khác biệt đáng kể so với ...
supercat

... "Đợi N + 1 giây rồi thực hiện một số hành động khác", do đó, thực tế là tập hợp các hành động có thể chịu được ngoài việc chờ đợi là trống rỗng sẽ không thể quan sát được. Mặt khác, nếu một đoạn mã loại bỏ một số hành động không thể chịu đựng được khỏi tập hợp các hành động có thể và một trong những hành động đó được thực hiện bằng mọi cách , thì điều đó nên được xem là có thể quan sát được. Thật không may, các quy tắc ngôn ngữ C và C ++ sử dụng từ "giả sử" theo một cách kỳ lạ không giống như bất kỳ lĩnh vực logic hay nỗ lực nào khác mà tôi có thể xác định.
supercat

1
@FamousJameis ok, nhưng Clang không chỉ xóa vòng lặp - nó sẽ phân tích tĩnh mọi thứ sau đó là không thể truy cập và phát ra một hướng dẫn không hợp lệ. Đó không phải là những gì bạn mong đợi nếu nó chỉ "loại bỏ" vòng lặp.
nneonneo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.