Làm thế nào để tôi biết nếu trình biên dịch đã phá vỡ mã của tôi và tôi phải làm gì nếu đó là trình biên dịch?


14

Thỉnh thoảng mã C ++ sẽ không hoạt động khi được biên dịch với một số mức tối ưu hóa. Nó có thể là trình biên dịch thực hiện tối ưu hóa phá vỡ mã hoặc nó có thể là mã chứa hành vi không xác định cho phép trình biên dịch làm bất cứ điều gì nó cảm thấy.

Giả sử tôi có một số đoạn mã bị phá vỡ khi được biên dịch với mức độ tối ưu hóa cao hơn. Làm thế nào để tôi biết nếu đó là mã hoặc trình biên dịch và tôi phải làm gì nếu đó là trình biên dịch?


43
Nhiều khả năng là bạn.
littleadv

9
@littleadv, ngay cả các phiên bản gần đây của gcc và msvc cũng có nhiều lỗi, vì vậy tôi không chắc chắn lắm.
SK-logic

3
Bạn đã bật tất cả các cảnh báo?

@ Thorbjørn Ravn Andersen: Vâng, tôi đã kích hoạt chúng.
sharptooth

3
FWIW: 1) Tôi cố gắng không làm bất cứ điều gì khó khăn có thể khiến trình biên dịch rối tung lên, 2) nơi duy nhất các cờ tối ưu hóa (đối với tốc độ) nằm trong mã mà bộ đếm chương trình dành một phần đáng kể thời gian của nó. Trừ khi bạn đang viết các vòng lặp cpu chặt chẽ, trong nhiều ứng dụng, PC chủ yếu dành toàn bộ thời gian cho các thư viện hoặc trong I / O. Trong loại ứng dụng đó, các công tắc / O hoàn toàn không giúp bạn.
Mike Dunlavey

Câu trả lời:


19

Tôi muốn nói rằng đó là một đặt cược an toàn rằng, trong phần lớn các trường hợp, đó là mã của bạn, không phải trình biên dịch, bị hỏng. Và ngay cả trong trường hợp đặc biệt khi nó là trình biên dịch, có lẽ bạn đang sử dụng một số tính năng ngôn ngữ tối nghĩa theo một cách khác thường, mà trình biên dịch cụ thể không được chuẩn bị; nói cách khác, rất có thể bạn có thể thay đổi mã của mình thành thành ngữ hơn và tránh điểm yếu của trình biên dịch.

Ở bất cứ giá nào, nếu bạn có thể chứng minh rằng bạn đã tìm thấy lỗi trình biên dịch (dựa trên thông số ngôn ngữ), hãy báo cáo cho nhà phát triển trình biên dịch, để họ có thể sửa nó một thời gian.


@ SK-logic, đủ công bằng, tôi không có số liệu thống kê để sao lưu nó. Nó dựa trên kinh nghiệm của riêng tôi và tôi thừa nhận rằng tôi hiếm khi kéo dài giới hạn của ngôn ngữ và / hoặc trình biên dịch - những người khác có thể làm điều đó thường xuyên hơn.
Péter Török

(1) @ SK-Logic: Chỉ tìm thấy một lỗi trình biên dịch C ++, cùng mã, đã thử trên một trình biên dịch và hoạt động, đã thử trong một trình biên dịch khác.
umlcat

8
@umlcat: rất có thể đó là mã của bạn tùy thuộc vào hành vi không xác định; trên một trình biên dịch, nó phù hợp với mong đợi của bạn, trên một trình biên dịch khác thì không. điều đó không có nghĩa là nó bị hỏng.
Javier

@Ritch Melton, bạn đã bao giờ sử dụng LTO chưa?
SK-logic

1
Tôi đồng ý với Crashworks, khi nói về máy chơi game. Không có gì lạ thường khi tìm thấy các trình biên dịch bí truyền trong tình huống cụ thể đó. Tuy nhiên, nếu bạn đang nhắm mục tiêu vào các PC bình thường, bằng cách sử dụng trình biên dịch được sử dụng nhiều, thì rất có thể bạn sẽ gặp phải một lỗi trình biên dịch mà chưa ai từng thấy trước đây.
Trevor Powell

14

Cũng như bình thường, như với bất kỳ lỗi nào khác: thực hiện một thử nghiệm được kiểm soát. Thu hẹp khu vực đáng ngờ, tắt tối ưu hóa mọi thứ khác và bắt đầu thay đổi các tối ưu hóa được áp dụng cho đoạn mã đó. Khi bạn có được khả năng tái tạo 100%, hãy bắt đầu thay đổi mã của mình, giới thiệu những thứ có thể phá vỡ sự tối ưu hóa nhất định (ví dụ: giới thiệu bí danh con trỏ có thể, chèn các cuộc gọi bên ngoài với các tác dụng phụ tiềm ẩn, v.v.). Nhìn vào mã lắp ráp trong trình gỡ lỗi cũng có thể giúp ích.


có thể giúp gì với? Nếu đó là một lỗi biên dịch - vậy thì sao?
littleadv

2
@littleadv, nếu đó là lỗi trình biên dịch, bạn có thể cố gắng sửa nó (hoặc chỉ báo cáo đúng, chi tiết đầy đủ) hoặc bạn có thể tìm hiểu cách tránh nó trong tương lai, nếu bạn không muốn sử dụng nó trong tương lai phiên bản trình biên dịch của bạn trong một thời gian. Nếu đó là một cái gì đó với mã của riêng bạn, một trong vô số các vấn đề về đường biên giới C ++ - thì việc xem xét kỹ lưỡng này cũng giúp khắc phục lỗi và tránh loại này trong tương lai.
SK-logic

Vì vậy, như tôi đã nói trong câu trả lời của mình - ngoài báo cáo, không có nhiều khác biệt trong điều trị, bất kể đó là lỗi của ai.
littleadv

3
@littleadv, không hiểu bản chất của một vấn đề bạn có thể phải đối mặt với nó nhiều lần. Và thường có khả năng tự sửa một trình biên dịch. Và, vâng, thật không may là "không thể" tìm thấy một lỗi trong trình biên dịch C ++.
SK-logic

10

Kiểm tra mã lắp ráp dẫn đến kết quả và xem liệu nó có thực hiện được nguồn của bạn không. Hãy nhớ rằng tỷ lệ cược rất cao rằng đó thực sự là mã của bạn có lỗi trong một số cách không rõ ràng.


1
Đây thực sự là câu trả lời duy nhất cho câu hỏi này. Công việc biên dịch, trong trường hợp này là đưa bạn từ C ++ sang ngôn ngữ hợp ngữ. Bạn nghĩ rằng nó là trình biên dịch ... kiểm tra trình biên dịch làm việc. Nó là đơn giản.
old_timer

7

Trong hơn 30 năm lập trình, số lỗi trình biên dịch (tạo mã) chính hãng mà tôi tìm thấy vẫn chỉ là ~ 10. Số lỗi của chính tôi (và của người khác) mà tôi đã tìm thấy và sửa trong cùng thời gian có lẽ là > 10.000. "Quy tắc ngón tay cái" của tôi là xác suất của bất kỳ lỗi nào do trình biên dịch là <0,001.


1
Bạn thật may mắn. Trung bình của tôi là khoảng 1 lỗi thực sự xấu mỗi tháng và các vấn đề biên giới nhỏ là cách thường xuyên hơn nhiều. Và mức độ tối ưu hóa cao hơn mà bạn đang sử dụng, cơ hội lỗi trình biên dịch càng cao. Nếu bạn đang cố gắng sử dụng -O3 và LTO, bạn sẽ rất may mắn khi không tìm thấy một vài người trong số họ ngay lập tức. Và tôi chỉ tính ở đây các lỗi trong các phiên bản phát hành - với tư cách là nhà phát triển trình biên dịch, tôi đang đối mặt với nhiều vấn đề tương tự như vậy trong công việc của mình, nhưng điều đó không được tính. Tôi chỉ biết làm thế nào dễ dàng để làm hỏng trình biên dịch.
SK-logic

2
25 năm và tôi cũng đã thấy rất nhiều. Các trình biên dịch đang trở nên tồi tệ hơn mỗi năm.
old_timer

5

Tôi bắt đầu viết một bình luận và sau đó quyết định nó quá dài và quá nhiều đến mức.

Tôi sẽ tranh luận rằng đó là mã của bạn bị hỏng. Trong trường hợp không chắc là bạn đã phát hiện ra một lỗi trong trình biên dịch - bạn nên báo cáo nó cho các nhà phát triển trình biên dịch, nhưng đó là nơi sự khác biệt kết thúc.

Giải pháp là xác định cấu trúc vi phạm và cấu trúc lại nó để nó sẽ thực hiện cùng một logic khác nhau. Điều đó rất có thể sẽ giải quyết vấn đề, cho dù lỗi nằm ở phía bạn hay trong trình biên dịch.


5
  1. Đọc lại mã của bạn thorougly. Hãy chắc chắn rằng bạn không làm những việc có tác dụng phụ trong ASSERT hoặc các câu lệnh cụ thể gỡ lỗi (hoặc tổng quát hơn, cấu hình) khác. Cũng cần nhớ rằng trong một bản dựng gỡ lỗi, bộ nhớ được khởi tạo khác nhau - các giá trị con trỏ thông báo bạn có thể kiểm tra tại đây: Gỡ lỗi - Đại diện phân bổ bộ nhớ . Khi chạy từ bên trong Visual Studio, bạn gần như luôn luôn sử dụng Debug Heap (ngay cả trong chế độ phát hành) trừ khi bạn chỉ định rõ ràng với một biến môi trường rằng đây không phải là điều bạn muốn.
  2. Kiểm tra bản dựng của bạn. Việc gặp sự cố với các bản dựng phức tạp ở những nơi khác ngoài trình biên dịch thực tế là rất phổ biến - sự phụ thuộc thường là thủ phạm. Tôi biết rằng "bạn đã thử xây dựng lại hoàn toàn" gần như là một câu trả lời gây phẫn nộ như "bạn đã thử cài đặt lại các cửa sổ chưa", nhưng nó thường giúp ích. Hãy thử: a) Khởi động lại. b) Xóa tất cả các tập tin trung gian và đầu ra của bạn MANUALLY và xây dựng lại.
  3. Xem qua mã của bạn để kiểm tra bất kỳ vị trí tiềm năng nào mà bạn có thể đang gọi hành vi không xác định. Nếu bạn đã làm việc trong C ++ được một thời gian, bạn sẽ biết có một số điểm mà bạn nghĩ rằng "Tôi không chắc chắn rằng tôi được phép giả định rằng ..." - google nó hoặc hỏi ở đây về điều đó cụ thể loại mã để xem liệu đó có phải là hành vi không xác định hay không.
  4. Nếu điều đó vẫn không xảy ra, hãy tạo đầu ra được xử lý trước cho tệp gây ra sự cố. Việc mở rộng macro bất ngờ có thể gây ra tất cả các loại niềm vui (tôi nhớ lại thời gian một đồng nghiệp quyết định một macro có tên H sẽ là một ý tưởng hay ...). Kiểm tra đầu ra được xử lý trước để biết những thay đổi bất ngờ giữa các cấu hình dự án của bạn.
  5. Phương án cuối cùng - bây giờ bạn thực sự đang ở vùng đất biên dịch lỗi - hãy nhìn vào đầu ra lắp ráp. Điều này có thể cần một số hoạt động đào và chiến đấu chỉ để nắm được những gì tổ hợp đang thực sự làm, nhưng nó thực sự khá nhiều thông tin. Bạn cũng có thể sử dụng các kỹ năng bạn chọn ở đây để đánh giá tối ưu hóa vi mô, vì vậy tất cả không bị mất.

+1 cho "hành vi không xác định." Tôi đã bị cắn bởi cái đó. Đã viết một số mã phụ thuộc vào int + inttràn chính xác như thể nó được biên dịch thành một lệnh ADD phần cứng. Nó chỉ hoạt động tốt khi được biên dịch với phiên bản GCC cũ hơn, nhưng không hoạt động khi được biên dịch với trình biên dịch mới hơn. Rõ ràng những người tốt bụng tại GCC đã quyết định rằng do kết quả của một tràn số nguyên không được xác định, trình tối ưu hóa của họ có thể hoạt động theo giả định rằng điều đó không bao giờ xảy ra. Nó tối ưu hóa một nhánh quan trọng ngay từ mã.
Solomon chậm

2

Nếu bạn muốn biết đó là mã của bạn hay trình biên dịch, bạn phải hoàn toàn biết đặc tả của C ++.

Nếu nghi ngờ vẫn còn, bạn phải hoàn toàn biết lắp ráp x86.

Nếu bạn không có tâm trạng học cả hai để hoàn thiện, thì gần như chắc chắn đó là một hành vi không xác định mà trình biên dịch của bạn giải quyết khác nhau tùy thuộc vào mức độ tối ưu hóa.


(+1) @mouviciel: Nó cũng phụ thuộc vào tính năng được trình biên dịch hỗ trợ, thậm chí, nếu nó có trong đặc tả. Tôi có một lỗi kỳ lạ với gcc. Tôi khai báo "cấu trúc c đơn giản" bằng "con trỏ hàm", được cho phép trong đặc tả, nhưng hoạt động trong một số trường hợp và không hoạt động trong trường hợp khác.
umlcat

1

Nhận được một lỗi biên dịch trên mã tiêu chuẩn hoặc một lỗi biên dịch nội bộ có nhiều khả năng hơn các trình tối ưu hóa bị sai. Nhưng tôi đã nghe nói về trình biên dịch tối ưu hóa các vòng lặp không chính xác mà quên một số tác dụng phụ một nguyên nhân phương pháp.

Tôi không có đề xuất về làm thế nào để biết nếu đó là bạn hoặc trình biên dịch. Bạn có thể thử một trình biên dịch khác.

Một ngày nọ tôi tự hỏi liệu đó có phải là mã của tôi hay không và ai đó đã đề nghị valgrind cho tôi. Tôi đã dành 5 hoặc 10 phút để chạy chương trình của mình với nó (tôi nghĩvalgrind --leak-check=yes myprog arg1 arg2 đã làm nhưng tôi đã chơi với các tùy chọn khác) và nó ngay lập tức cho tôi thấy một dòng chạy trong một trường hợp cụ thể là vấn đề. Sau đó, ứng dụng của tôi chạy trơn tru kể từ khi không có sự cố, lỗi hoặc hành vi lạ. valgrind hoặc một công cụ khác giống như nó là một cách tốt để biết nếu đó là mã của bạn.

Lưu ý bên lề: Tôi đã từng tự hỏi tại sao hiệu suất của ứng dụng của tôi bị hút. Hóa ra tất cả các vấn đề về hiệu suất của tôi cũng nằm trong một dòng. Tôi đã viết for(int i=0; i<strlen(sz); ++i) {. Các sz là một vài mb. Vì một số lý do, trình biên dịch đã chạy strlen mỗi lần ngay cả sau khi tối ưu hóa. Một dòng có thể là một vấn đề lớn. Từ màn trình diễn đến tai nạn


1

Một tình huống ngày càng phổ biến là các trình biên dịch phá mã được viết cho các phương ngữ của C mà các hành vi được hỗ trợ không bắt buộc theo Tiêu chuẩn và cho phép mã nhắm mục tiêu các phương ngữ đó hiệu quả hơn mã tuân thủ nghiêm ngặt. Trong trường hợp như vậy, sẽ không công bằng khi mô tả mã "bị hỏng", đáng tin cậy 100% trên các trình biên dịch đã thực hiện phương ngữ đích hoặc mô tả là "bị hỏng" trình biên dịch xử lý một phương ngữ không hỗ trợ ngữ nghĩa cần thiết . Thay vào đó, các vấn đề đơn giản xuất phát từ việc ngôn ngữ được xử lý bởi các trình biên dịch hiện đại có bật tối ưu hóa đang chuyển hướng khỏi các phương ngữ đã từng phổ biến (và vẫn được xử lý bởi nhiều trình biên dịch với tối ưu hóa bị vô hiệu hóa hoặc bởi một số ngay cả khi tối ưu hóa được bật).

Ví dụ, rất nhiều mã được viết cho các phương ngữ nhận ra là hợp pháp một số mẫu bí danh con trỏ không bắt buộc theo cách giải thích của Tiêu chuẩn gcc và sử dụng các mẫu đó để cho phép bản dịch mã đơn giản dễ đọc và hiệu quả hơn hơn là có thể theo cách giải thích của gcc về Tiêu chuẩn C. Mã như vậy có thể không tương thích với gcc, nhưng điều đó không có nghĩa là nó bị hỏng. Nó chỉ đơn giản dựa vào các tiện ích mở rộng mà gcc chỉ hỗ trợ khi tối ưu hóa bị vô hiệu hóa.


Chà, chắc chắn không có gì sai khi mã hóa các phần mở rộng X và Y tiêu chuẩn C, miễn là điều đó mang lại cho bạn những lợi thế đáng kể, bạn biết bạn đã làm điều đó và bạn đã ghi chép lại kỹ lưỡng. Đáng buồn thay, cả ba điều kiện thường không được đáp ứng , và do đó công bằng khi nói mã bị hỏng.
Ded repeatator

@Ded repeatator: Trình biên dịch C89 được quảng bá là tương thích hướng lên với các phiên bản trước và tương tự đối với C99, v.v. các nền tảng đã coi hành vi như được xác định nên tiếp tục làm như vậy; lý do cho việc quảng bá các loại không dấu ngắn thành các dấu hiệu sẽ gợi ý rằng các tác giả của Trình biên dịch dự kiến ​​Tiêu chuẩn sẽ hành xử theo cách đó cho dù Tiêu chuẩn có bắt buộc hay không. Hơn nữa ...
supercat

... Việc giải thích chặt chẽ các quy tắc răng cưa sẽ ném khả năng tương thích lên trên cửa sổ và khiến nhiều loại mã không thể thực hiện được, nhưng một vài điều chỉnh nhỏ (ví dụ: xác định một số mẫu trong đó nên đặt bí danh kiểu chéo và do đó cho phép) sẽ giải quyết cả hai vấn đề . Toàn bộ mục đích đã nêu của quy tắc là để tránh yêu cầu trình biên dịch đưa ra các giả định răng cưa "bi quan", nhưng đưa ra "float x", nên giả định rằng "foo ((int *) & x)" có thể sửa đổi x ngay cả khi "foo" không 'không viết cho bất kỳ con trỏ nào thuộc loại' float * "hoặc" char * "được coi là" bi quan "hay" hiển nhiên "?
supercat

0

Cô lập điểm có vấn đề và so sánh hành vi được quan sát với những gì sẽ xảy ra theo thông số ngôn ngữ. Chắc chắn không dễ dàng, nhưng đó là những gì bạn phải làm gì để biết (và không chỉ đảm nhận ).

Tôi có lẽ sẽ không tỉ mỉ đến thế. Thay vào đó, tôi sẽ hỏi danh sách hỗ trợ / diễn đàn gửi thư của nhà sản xuất trình biên dịch. Nếu nó thực sự là một lỗi trong trình biên dịch, thì họ có thể sửa nó. Có lẽ nó sẽ là mã của tôi. Ví dụ, thông số kỹ thuật ngôn ngữ liên quan đến khả năng hiển thị bộ nhớ trong luồng có thể khá phản trực giác và chúng chỉ có thể trở nên rõ ràng khi sử dụng một số cờ tối ưu hóa cụ thể, trên một số phần cứng cụ thể (!). Một số hành vi có thể không được xác định bởi thông số kỹ thuật, vì vậy nó có thể hoạt động với một số trình biên dịch / một số cờ và không hoạt động với một số khác, v.v.


0

Rất có thể mã của bạn có một số hành vi không xác định (như những người khác đã giải thích, bạn có nhiều khả năng có lỗi trong mã hơn trình biên dịch, ngay cả khi trình biên dịch C ++ phức tạp đến mức chúng có lỗi; ngay cả đặc tả C ++ cũng có lỗi thiết kế) . Và UB có thể ở đây ngay cả khi thực thi được biên dịch xảy ra để làm việc (do không may mắn).

Vì vậy, bạn nên đọc blog của Lattner Những gì mỗi lập trình viên C nên biết về hành vi không xác định (hầu hết cũng áp dụng cho C ++ 11).

Công cụ valgrind và các -fsanitize= tùy chọn thiết bị gần đây cho GCC (hoặc Clang / LLVM ), cũng sẽ hữu ích. Và tất nhiên, cho phép tất cả các cảnh báo:g++ -Wall -Wextra

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.