printf - nguồn lỗi? [đóng cửa]


9

Tôi đang sử dụng rất nhiều printfcho mục đích truy tìm / ghi nhật ký trong mã của mình, tôi thấy rằng đó là một nguồn lỗi lập trình. Tôi luôn thấy toán tử chèn ( <<) có phần kỳ quặc nhưng tôi bắt đầu nghĩ rằng bằng cách sử dụng nó thay vì tôi có thể tránh được một số lỗi này.

Bất cứ ai cũng từng có một tiết lộ tương tự hoặc tôi chỉ đang nắm lấy ống hút ở đây?

Một số điểm lấy đi

  • Dòng suy nghĩ hiện tại của tôi là loại an toàn vượt trội hơn bất kỳ lợi ích nào của việc sử dụng printf. Vấn đề thực sự là chuỗi định dạng và việc sử dụng các hàm matrixdic không an toàn.
  • Có thể tôi sẽ không sử dụng <<và các biến thể luồng đầu ra stl nhưng tôi chắc chắn sẽ xem xét sử dụng một cơ chế an toàn kiểu rất giống nhau.
  • Rất nhiều dấu vết / ghi nhật ký là có điều kiện nhưng tôi muốn luôn luôn chạy mã để không bỏ sót lỗi trong các bài kiểm tra chỉ vì đó là một nhánh hiếm khi được thực hiện.

4
printftrong thế giới C ++? Tôi đang thiếu một cái gì đó ở đây?
dùng827992

10
@ user827992: Bạn có thiếu thực tế là tiêu chuẩn C ++ bao gồm thư viện chuẩn C theo tham chiếu không? Nó hoàn toàn hợp pháp để sử dụng printftrong C ++. (Cho dù đó là một ý tưởng tốt là một câu hỏi khác.)
Keith Thompson

2
@ user827992: printfkhông có một số lợi thế; xem câu trả lời của tôi
Keith Thompson

1
Câu hỏi này là ranh giới khá. "Các bạn nghĩ gì" các câu hỏi thường được đóng lại.
dbracey

1
@vitaut Tôi đoán (cảm ơn vì tiền boa). Tôi chỉ hơi bối rối vì sự kiểm duyệt mạnh mẽ. Nó không thực sự thúc đẩy các cuộc thảo luận thú vị về các tình huống lập trình, đó là điều tôi muốn có nhiều hơn.
John Leidegren

Câu trả lời:


2

printf, đặc biệt trong trường hợp bạn có thể quan tâm đến hiệu suất (như sprintf và fprintf) là một hack thực sự kỳ lạ. Nó liên tục làm tôi ngạc nhiên rằng những người dồn nén vào C ++ do hiệu năng rất nhỏ liên quan đến các chức năng ảo sau đó sẽ tiếp tục bảo vệ io của C.

Có, để tìm ra định dạng đầu ra của chúng tôi, một cái gì đó mà chúng tôi có thể biết 100% khi biên dịch, hãy phân tích một chuỗi định dạng dày đặc trong thời gian chạy bên trong một bảng nhảy kỳ lạ bằng cách sử dụng mã định dạng không thể hiểu được!

Tất nhiên, các mã định dạng này không thể được tạo để khớp với các loại mà chúng đại diện, điều đó quá dễ dàng ... và bạn được nhắc nhở mỗi khi bạn tra cứu xem liệu% llg hay% lg mà ngôn ngữ (được gõ mạnh) này tạo ra cho bạn tìm ra các loại thủ công để in / quét thứ gì đó, VÀ được thiết kế cho các bộ xử lý trước 32 bit.

Tôi sẽ thừa nhận rằng việc xử lý độ rộng và độ chính xác định dạng của C ++ là cồng kềnh và có thể sử dụng một số đường cú pháp, nhưng điều đó không có nghĩa là bạn phải bảo vệ bản hack kỳ quái đó là hệ thống io chính của C. Các vấn đề cơ bản tuyệt đối khá dễ dàng trong cả hai ngôn ngữ (mặc dù bạn có thể nên sử dụng một cái gì đó như hàm lỗi / luồng lỗi tùy chỉnh cho dù sao mã gỡ lỗi), các trường hợp vừa phải giống như regex (dễ viết, khó phân tích / gỡ lỗi ) và các trường hợp phức tạp không thể có trong C.

(Nếu bạn hoàn toàn sử dụng các thùng chứa tiêu chuẩn, hãy tự viết cho mình một toán tử templated nhanh << quá tải cho phép bạn làm những việc như std::cout << my_list << "\n";để gỡ lỗi, trong đó my_list thuộc loại list<vector<pair<int,string> > >.)


1
Vấn đề của thư viện C ++ tiêu chuẩn là, hầu hết các hóa thân thực hiện operator<<(ostream&, T)bằng cách gọi ... tốt , sprintf! Hiệu suất của sprintfkhông tối ưu, nhưng do đó, hiệu suất của iostreams thậm chí còn tệ hơn.
Jan Hudec

@JanHudec: Điều đó đã không đúng trong khoảng một thập kỷ tại thời điểm này. Việc in thực tế được thực hiện với cùng các lệnh gọi hệ thống cơ bản và các triển khai C ++ thường gọi vào các thư viện C cho điều đó ... nhưng đó không giống như định tuyến std :: cout thông qua printf.
jkerian

16

Trộn đầu ra kiểu C printf()(hoặc puts()hoặc putchar()hoặc ...) với đầu ra kiểu C ++ std::cout << ...có thể không an toàn. Nếu tôi nhớ lại một cách chính xác, chúng có thể có các cơ chế đệm riêng biệt, do đó đầu ra có thể không xuất hiện theo thứ tự dự định. (Như AProgrammer đề cập trong một bình luận, sync_with_stdiogiải quyết vấn đề này).

printf()về cơ bản là không an toàn. Loại dự kiến ​​cho một đối số được xác định bởi chuỗi định dạng ( "%d"yêu cầu inthoặc một thứ gì đó khuyến khích int, "%s"yêu cầu char*phải trỏ đến chuỗi kiểu C kết thúc chính xác, v.v.), nhưng chuyển loại đối số sai dẫn đến hành vi không xác định , không phải là một lỗi có thể chẩn đoán. Một số trình biên dịch, chẳng hạn như gcc, thực hiện công việc cảnh báo khá hợp lý về kiểu không khớp, nhưng chúng chỉ có thể làm như vậy nếu chuỗi định dạng là một chữ hoặc được biết đến vào thời gian biên dịch (là trường hợp phổ biến nhất) - và như vậy cảnh báo không được yêu cầu bởi ngôn ngữ. Nếu bạn vượt qua loại lập luận sai, những điều xấu tùy tiện có thể xảy ra.

Mặt khác, luồng I / O của C ++ an toàn hơn nhiều loại, vì <<toán tử bị quá tải cho nhiều loại khác nhau. std::cout << xkhông phải chỉ định loại x; trình biên dịch sẽ tạo mã đúng cho bất kỳ loại nào xcó.

Mặt khác, printfcác tùy chọn định dạng của IMHO thuận tiện hơn nhiều. Nếu tôi muốn in một giá trị dấu phẩy động có 3 chữ số sau dấu thập phân, tôi có thể sử dụng "%.3f"- và nó không có tác dụng đối với các đối số khác, ngay cả trong cùng một printfcuộc gọi. setprecisionMặt khác, C ++ ảnh hưởng đến trạng thái của luồng và có thể gây rối cho đầu ra sau này nếu bạn không cẩn thận khôi phục luồng về trạng thái trước đó. (Đây là tiểu thư thú cưng của tôi; nếu tôi thiếu một số cách sạch sẽ để tránh nó, xin vui lòng bình luận.)

Cả hai đều có ưu điểm và nhược điểm. Tính khả dụng của printfđặc biệt hữu ích nếu bạn có nền C và bạn quen thuộc hơn với nó hoặc nếu bạn đang nhập mã nguồn C vào chương trình C ++. std::cout << ...là thành ngữ hơn cho C ++ và không cần nhiều sự quan tâm để tránh sự không phù hợp với kiểu. Cả hai đều là C ++ hợp lệ (tiêu chuẩn C ++ bao gồm hầu hết thư viện chuẩn C theo tham chiếu).

lẽ tốt nhất là sử dụng std::cout << ...cho các lập trình viên C ++ khác, những người có thể làm việc với mã của bạn, nhưng bạn có thể sử dụng một trong hai - đặc biệt là trong mã theo dõi mà bạn sẽ vứt đi.

Và tất nhiên, đáng để dành thời gian học cách sử dụng trình gỡ lỗi (nhưng điều đó có thể không khả thi trong một số môi trường).


Không đề cập đến việc trộn trong câu hỏi ban đầu.
dbracey

1
@dbracey: Không, nhưng tôi nghĩ rằng nó đáng được đề cập đến như một nhược điểm có thể có printf.
Keith Thompson

6
Đối với vấn đề đồng bộ hóa, xem std::ios_base::sync_with_stdio.
AProgrammer

1
+1 Sử dụng std :: cout để in thông tin gỡ lỗi trong ứng dụng đa luồng là vô dụng 100%. Ít nhất là với những thứ printf không có khả năng bị xen kẽ và không thể nhìn thấy được bởi con người hoặc máy móc.
James

@James: Có phải vì đó std::coutsử dụng một cuộc gọi riêng cho từng mục được in? Bạn có thể giải quyết vấn đề đó bằng cách thu thập một dòng đầu ra thành một chuỗi trước khi in nó. Và tất nhiên bạn cũng có thể in một mục cùng một lúc printf; Thật tiện lợi hơn khi in một dòng (hoặc nhiều hơn) trong một cuộc gọi.
Keith Thompson

2

Vấn đề của bạn rất có thể đến từ sự pha trộn của hai trình quản lý đầu ra tiêu chuẩn rất khác nhau, mỗi trình quản lý đều có chương trình nghị sự riêng cho STDOUT nhỏ đáng thương đó. Bạn không có gì đảm bảo về cách chúng được triển khai và hoàn toàn có thể chúng đặt các tùy chọn mô tả tệp xung đột, cả hai đều cố gắng thực hiện những điều khác nhau với nó, v.v. Ngoài ra, các toán tử chèn có một chính printf: printfsẽ cho phép bạn làm điều này:

printf("%d", SomeObject);

Trong khi đó <<sẽ không.

Lưu ý: Để gỡ lỗi, bạn không sử dụng printfhoặc cout. Bạn sử dụng fprintf(stderr, ...)cerr.


Không đề cập đến việc trộn trong câu hỏi ban đầu.
dbracey

Tất nhiên bạn có thể in địa chỉ của một đối tượng nhưng sự khác biệt lớn printflà không an toàn về loại hình và dòng suy nghĩ hiện tại của tôi là an toàn loại vượt trội hơn bất kỳ lợi ích nào khi sử dụng printf. Vấn đề thực sự là chuỗi định dạng và hàm matrixdic không an toàn kiểu.
John Leidegren

@JohnLeidegren: Nhưng nếu không SomeObjectphải là một con trỏ thì sao? YOur sẽ nhận dữ liệu nhị phân tùy ý mà trình biên dịch quyết định đại diện SomeObject.
Linuxios

Tôi nghĩ rằng tôi đã đọc câu trả lời của bạn ngược ... nvm.
John Leidegren

1

Có nhiều nhóm - ví dụ google - không thích stream.

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(Pop mở tam giác điều để bạn có thể xem cuộc thảo luận.) Tôi nghĩ rằng hướng dẫn phong cách google C ++ có RẤT NHIỀU lời khuyên rất hợp lý.

Tôi nghĩ rằng sự đánh đổi là các luồng an toàn hơn nhưng printf rõ ràng hơn để đọc (và dễ dàng hơn để có được chính xác định dạng bạn muốn).


2
Hướng dẫn về phong cách Google rất hay, NHƯNG nó chứa khá nhiều mục không phù hợp với hướng dẫn mục đích chung . (không sao, vì sau tất cả, đó là hướng dẫn của Google về mã chạy tại / cho Google.)
Martin Ba

1

printfcó thể gây ra lỗi do thiếu an toàn loại. Có một vài cách để giải quyết vấn đề mà không chuyển sang iostream's <<điều hành và nhiều hơn nữa phức tạp định dạng:

  • Một số trình biên dịch (như GCC và Clang) có thể tùy ý kiểm tra các printfchuỗi định dạng của bạn theo các printfđối số và có thể hiển thị các cảnh báo như sau nếu chúng không khớp.
    cảnh báo: chuyển đổi chỉ định loại 'int' nhưng đối số có loại 'char *'
  • Các typesafeprintf kịch bản có thể preprocess bạn printfcác cuộc gọi kiểu để làm cho họ gõ-an toàn.
  • Các thư viện như Boost.FormatFastFormat cho phép bạn sử dụng các printfchuỗi định dạng giống như (đặc biệt là Boost.Format gần như giống hệt nhau printf) trong khi vẫn giữ được iostreamsđộ an toàn và loại mở rộng.

1

Cú pháp Printf về cơ bản là tốt, trừ một số gõ khó hiểu. Nếu bạn nghĩ nó sai tại sao C #, Python và các ngôn ngữ khác sử dụng cấu trúc rất giống nhau? Vấn đề trong C hoặc C ++: nó không phải là một phần của ngôn ngữ và do đó trình biên dịch không kiểm tra đúng cú pháp (*) và không bị phân tách thành một loạt các cuộc gọi riêng nếu tối ưu hóa tốc độ. Lưu ý rằng nếu tối ưu hóa kích thước, các cuộc gọi printf có thể hiệu quả hơn! Cú pháp phát trực tuyến C ++ là imho bất cứ điều gì nhưng tốt. Nó hoạt động, loại an toàn là có, nhưng cú pháp dài dòng ... bleh. Tôi có nghĩa là tôi sử dụng nó, nhưng không có niềm vui.

(*) một số trình biên dịch Thực hiện kiểm tra này cộng với hầu hết tất cả các công cụ phân tích tĩnh (Tôi sử dụng Lint và không bao giờ có bất kỳ vấn đề nào với printf kể từ đó).


1
Boost.Format kết hợp cú pháp thuận tiện ( format("fmt") % arg1 % arg2 ...;) với loại an toàn. Với chi phí của một số hiệu suất cao hơn, bởi vì nó tạo ra các cuộc gọi chuỗi mà bên trong tạo ra các cuộc gọi sprintf trong nhiều triển khai.
Jan Hudec

0

printftheo ý kiến ​​riêng của tôi, một công cụ đầu ra linh hoạt hơn nhiều để xử lý các biến so với bất kỳ đầu ra luồng CPP nào. Ví dụ:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

Tuy nhiên, nơi bạn có thể muốn sử dụng <<toán tử CPP là khi bạn quá tải nó cho một phương thức cụ thể ... ví dụ để lấy một bãi chứa một đối tượng chứa dữ liệu của một người cụ thể, PersonData....

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

Vì thế, sẽ hiệu quả hơn rất nhiều khi nói (giả sử alà một đối tượng của PersonData)

std::cout << a;

hơn:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

Cái trước phù hợp hơn nhiều với nguyên tắc đóng gói (Không cần biết chi tiết cụ thể, biến thành viên riêng), và cũng dễ đọc hơn.


0

Bạn không cần phải sử dụng printftrong C ++. Không bao giờ. Lý do là, như bạn đã lưu ý chính xác, đó là nguồn gây ra lỗi và thực tế là in các loại tùy chỉnh và trong C ++, hầu hết mọi thứ đều phải là loại tùy chỉnh, là nỗi đau. Giải pháp C ++ là các luồng.

Tuy nhiên, có một vấn đề nghiêm trọng khiến các luồng không phù hợp với bất kỳ đầu ra nào và người dùng có thể nhìn thấy! Vấn đề là bản dịch. Ví dụ mượn từ hướng dẫn sử dụng gettext nói rằng bạn muốn viết:

cout << "String '" << str << "' has " << str.size() << " characters\n";

Bây giờ người dịch tiếng Đức đi cùng và nói: Ok, bằng tiếng Đức, thông điệp nên là

n Zeichen lang ist die Zeichenkette ' s '

Và bây giờ bạn đang gặp rắc rối, bởi vì anh ấy cần những mảnh ghép xung quanh. Cần phải nói rằng, thậm chí nhiều người thực hiện printfcó vấn đề với điều này. Trừ khi họ hỗ trợ tiện ích mở rộng để bạn có thể sử dụng

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

Các Boost.Format hỗ trợ các định dạng printf-phong cách và có tính năng này. Vì vậy, bạn viết:

cout << format("String '%1' has %2 characters\n") % str % str.size();

Thật không may, nó mang một chút hình phạt hiệu năng, bởi vì bên trong nó tạo ra một chuỗi và sử dụng <<toán tử để định dạng từng bit và trong nhiều lần thực hiện mà <<toán tử gọi bên trong sprintf. Tôi nghi ngờ thực hiện hiệu quả hơn sẽ có thể nếu thực sự mong muốn.


-1

Bạn đang làm rất nhiều công việc vô ích, bên cạnh sự thật stllà xấu xa hay không, hãy gỡ lỗi mã của bạn với một loạt printfchỉ thêm 1 cấp độ thất bại có thể xảy ra.

Chỉ cần sử dụng trình gỡ lỗi và đọc một cái gì đó về Ngoại lệ và cách bắt và ném chúng; cố gắng để không dài dòng hơn bạn thực sự cần phải được.

PS

printf được sử dụng trong C, đối với C ++ bạn có std::cout


Bạn không sử dụng theo dõi / ghi nhật ký thay vì trình gỡ lỗi.
John Leidegren
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.