Tại sao printf với một đối số duy nhất (không có chỉ định chuyển đổi) không được dùng nữa?


102

Trong một cuốn sách mà tôi đang đọc, nó được viết rằng printfvới một đối số duy nhất (không có chỉ định chuyển đổi) không được dùng nữa. Nó khuyên bạn nên thay thế

printf("Hello World!");

với

puts("Hello World!");

hoặc là

printf("%s", "Hello World!");

Ai đó có thể cho tôi biết tại sao printf("Hello World!");là sai? Trong cuốn sách có viết rằng nó có chứa các lỗ hổng. Những lỗ hổng này là gì?


34
Lưu ý: printf("Hello World!")không giống như puts("Hello World!"). puts()phụ lục a '\n'. Thay vì so sánh printf("abc")đểfputs("abc", stdout)
Chux - Khôi phục Monica

5
Cuốn sách đó là gì? Tôi nghĩ printfkhông bị phản đối giống như cách mà ví dụ như getskhông được dùng trong C99, vì vậy bạn có thể cân nhắc chỉnh sửa câu hỏi của mình để chính xác hơn.
el.pescado

14
Có vẻ như cuốn sách bạn đang đọc không hay lắm - một cuốn sách hay không nên chỉ nói những điều như thế này là "không được chấp nhận" (điều đó thực tế là sai trừ khi tác giả sử dụng từ này để mô tả ý kiến ​​riêng của họ) và nên giải thích cách sử dụng thực sự không hợp lệ và nguy hiểm hơn là hiển thị mã an toàn / hợp lệ như một ví dụ về việc bạn "không nên làm".
R .. GitHub DỪNG TRỢ GIÚP NGAY LÚC NÀY

8
Bạn có thể xác định cuốn sách?
Keith Thompson,

7
Vui lòng ghi rõ tên sách, tác giả và trang tham khảo. Cám ơn.
Greenonline,

Câu trả lời:


122

printf("Hello World!"); IMHO không dễ bị tấn công nhưng hãy xem xét điều này:

const char *str;
...
printf(str);

Nếu strtình cờ trỏ đến một chuỗi có chứa mã %sđịnh dạng, chương trình của bạn sẽ thể hiện hành vi không xác định (chủ yếu là sự cố), trong khi puts(str)sẽ chỉ hiển thị chuỗi như vậy.

Thí dụ:

printf("%s");   //undefined behaviour (mostly crash)
puts("%s");     // displays "%s\n"

21
Ngoài việc gây ra sự cố chương trình, có nhiều cách khai thác khác có thể xảy ra với các chuỗi định dạng. Xem tại đây để biết thêm thông tin: en.wikipedia.org/wiki/Uncontrolled_format_string
e.dan

9
Một lý do khác là nó putscó lẽ sẽ nhanh hơn.
edmz

38
@black: puts"có lẽ là" nhanh hơn, và đây có lẽ là một lý do khác mà mọi người khuyên dùng nó, nhưng nó không thực sự nhanh hơn. Tôi vừa in "Hello, world!"1.000.000 lần, cả hai chiều. Với printfnó mất 0,92 giây. Với putsnó mất 0,93 giây. Có những điều cần lo lắng khi nói đến hiệu quả, nhưng printfvs. putskhông phải là một trong số đó.
Steve Summit

10
@KonstantinWeitz: Nhưng (a) Tôi không sử dụng gcc và (b) không quan trọng tại sao tuyên bố " putsnhanh hơn" là sai, nó vẫn sai.
Steve Summit

6
@KonstantinWeitz: Yêu cầu mà tôi cung cấp bằng chứng là (ngược lại với) khiếu nại mà người dùng da đen đưa ra. Tôi chỉ đang cố gắng làm rõ rằng các lập trình viên không nên lo lắng khi gọi putsvì lý do này. (Nhưng nếu bạn muốn tranh luận về nó: Tôi sẽ rất ngạc nhiên nếu bạn có thể tìm thấy bất kỳ trình biên dịch hiện đại nào cho bất kỳ machne hiện đại nào putsnhanh hơn đáng kể so với printfbất kỳ trường hợp nào.)
Steve Summit

75

printf("Hello world");

là tốt và không có lỗ hổng bảo mật.

Vấn đề nằm ở chỗ:

printf(p);

đâu plà một con trỏ đến đầu vào do người dùng kiểm soát. Nó dễ bị tấn công chuỗi định dạng : người dùng có thể chèn các thông số kỹ thuật chuyển đổi để kiểm soát chương trình, ví dụ: %xkết xuất bộ nhớ hoặc %nghi đè bộ nhớ.

Lưu ý rằng puts("Hello world")hành vi không tương đương với printf("Hello world")nhưng với printf("Hello world\n"). Các trình biên dịch thường đủ thông minh để tối ưu hóa cuộc gọi sau để thay thế nó bằng puts.


10
Tất nhiên printf(p,x)cũng sẽ có vấn đề nếu người dùng có quyền kiểm soát p. Vì vậy, vấn đề không phải là việc sử dụng printfchỉ với một đối số mà là với một chuỗi định dạng do người dùng kiểm soát.
Hagen von Eitzen

2
@HagenvonEitzen Điều đó đúng về mặt kỹ thuật, nhưng ít người cố tình sử dụng chuỗi định dạng do người dùng cung cấp. Khi mọi người viết printf(p), đó là bởi vì họ không nhận ra rằng đó là một chuỗi định dạng, họ chỉ nghĩ rằng họ đang in một chữ.
Barmar

33

Ngoài các câu trả lời khác, đây printf("Hello world! I am 50% happy today")là một lỗi dễ mắc phải, có khả năng gây ra tất cả các vấn đề khó chịu về bộ nhớ (đó là UB!).

Nó chỉ đơn giản hơn, dễ dàng hơn và mạnh mẽ hơn để "yêu cầu" các lập trình viên phải hoàn toàn rõ ràng khi họ muốn một chuỗi nguyên văn và không có gì khác .

Và đó là những gì printf("%s", "Hello world! I am 50% happy today")giúp bạn. Nó hoàn toàn an toàn.

(Steve, tất nhiên printf("He has %d cherries\n", ncherries)là hoàn toàn không phải như vậy; trong trường hợp này, lập trình viên không ở trong tư duy "chuỗi nguyên văn"; cô ấy ở trong tư duy "chuỗi định dạng".)


2
Điều này không đáng để tranh cãi và tôi hiểu bạn đang nói gì về tư duy chuỗi định dạng so với nguyên văn, nhưng, tốt, không phải ai cũng nghĩ như vậy, đó là một lý do khiến các quy tắc một kích thước phù hợp với tất cả có thể xếp hạng. Nói "không bao giờ in chuỗi hằng với printf" giống hệt như nói "luôn viết if(NULL == p). Những quy tắc này có thể hữu ích cho một số lập trình viên, nhưng không phải tất cả. Và trong cả hai trường hợp ( printfđịnh dạng không khớp và điều kiện Yoda), các trình biên dịch hiện đại vẫn cảnh báo về những sai lầm, vì vậy các quy tắc nhân tạo thậm chí còn ít quan trọng hơn.
Steve Summit

1
@Steve Nếu chính xác là không có ưu điểm nào khi sử dụng thứ gì đó, nhưng có khá nhiều nhược điểm, thì thực sự không có lý do gì để sử dụng nó. Điều kiện Yoda mặt khác làm có nhược điểm là họ làm cho đoạn code khó đọc (bạn muốn bằng trực giác nói "nếu p là số không" không "nếu không là p").
Voo

2
@Voo printf("%s", "hello")sẽ chậm hơn printf("hello"), vì vậy có một nhược điểm. Một điều nhỏ, vì IO hầu như luôn luôn chậm hơn so với định dạng đơn giản như vậy, nhưng một nhược điểm.
Yakk - Adam Nevraumont

1
@Yakk Tôi nghi ngờ điều đó sẽ chậm hơn
MM

gcc -Wall -W -Werrorsẽ ngăn chặn những hậu quả xấu từ những sai lầm như vậy.
chqrlie

17

Tôi sẽ chỉ thêm một chút thông tin về phần lỗ hổng ở đây.

Nó được cho là dễ bị tấn công vì lỗ hổng định dạng chuỗi printf. Trong ví dụ của bạn, nơi chuỗi được mã hóa cứng, nó vô hại (ngay cả khi các chuỗi mã hóa cứng như thế này không bao giờ được khuyến khích hoàn toàn). Nhưng việc chỉ định các kiểu của tham số là một thói quen tốt nên thực hiện. Lấy ví dụ sau:

Nếu ai đó đặt ký tự chuỗi định dạng trong printf của bạn thay vì một chuỗi thông thường (giả sử, nếu bạn muốn in chương trình stdin), printf sẽ lấy bất cứ thứ gì có thể vào ngăn xếp.

Nó đã (và vẫn đang) rất được sử dụng để khai thác các chương trình vào việc khám phá các ngăn xếp để truy cập thông tin ẩn hoặc bỏ qua xác thực chẳng hạn.

Ví dụ (C):

int main(int argc, char *argv[])
{
    printf(argv[argc - 1]); // takes the first argument if it exists
}

nếu tôi đặt làm đầu vào của chương trình này "%08x %08x %08x %08x %08x\n"

printf ("%08x %08x %08x %08x %08x\n"); 

Điều này hướng dẫn hàm printf truy xuất năm tham số từ ngăn xếp và hiển thị chúng dưới dạng số thập lục phân có đệm 8 chữ số. Vì vậy, một đầu ra có thể có có thể trông giống như:

40012980 080628c4 bffff7a4 00000005 08059c04

Xem phần này để có lời giải thích đầy đủ hơn và các ví dụ khác.


13

Gọi printfbằng chuỗi định dạng chữ là an toàn và hiệu quả, đồng thời có các công cụ để tự động cảnh báo bạn nếu lệnh gọi của bạn printfvới chuỗi định dạng do người dùng cung cấp là không an toàn.

Các cuộc tấn công nghiêm trọng nhất vào việc printflợi dụng công %ncụ định dạng. Ngược lại với tất cả các mã định dạng khác, chẳng hạn %d, %nthực sự ghi một giá trị vào địa chỉ bộ nhớ được cung cấp trong một trong các đối số định dạng. Điều này có nghĩa là kẻ tấn công có thể ghi đè bộ nhớ và do đó có khả năng chiếm quyền kiểm soát chương trình của bạn. Wikipedia cung cấp thêm chi tiết.

Nếu bạn gọi printfvới một chuỗi định dạng chữ, kẻ tấn công không thể lẻn %nvào chuỗi định dạng của bạn và do đó bạn được an toàn. Trên thực tế, gcc sẽ thay đổi cuộc gọi của bạn printfthành cuộc gọi đến puts, vì vậy không có bất kỳ sự khác biệt nào (hãy kiểm tra điều này bằng cách chạy gcc -O3 -S).

Nếu bạn gọi printfbằng chuỗi định dạng do người dùng cung cấp, kẻ tấn công có thể lẻn %nvào chuỗi định dạng của bạn và chiếm quyền kiểm soát chương trình của bạn. Trình biên dịch của bạn thường sẽ cảnh báo bạn rằng nó không an toàn, hãy xem -Wformat-security. Ngoài ra còn có các công cụ nâng cao hơn đảm bảo rằng một lệnh gọi printfan toàn ngay cả với các chuỗi định dạng do người dùng cung cấp và chúng thậm chí có thể kiểm tra xem bạn có chuyển đúng số lượng và kiểu đối số hay không printf. Ví dụ, đối với Java có Lỗi Prone của GoogleKhung kiểm tra .


12

Đây là lời khuyên sai lầm. Có, nếu bạn có chuỗi thời gian chạy để in,

printf(str);

khá nguy hiểm và bạn nên luôn sử dụng

printf("%s", str);

thay vào đó, bởi vì nói chung bạn không bao giờ có thể biết liệu strcó thể chứa một %dấu hiệu hay không. Tuy nhiên, nếu bạn có một chuỗi hằng số thời gian biên dịch , không có gì sai với

printf("Hello, world!\n");

(Trong số những thứ khác, đó là chương trình C cổ điển nhất từ ​​trước đến nay, theo nghĩa đen từ cuốn sách lập trình C của Genesis. Vì vậy, bất kỳ ai phản đối cách sử dụng đó là hơi dị giáo, và tôi đối với một người sẽ hơi khó chịu!)


because printf's first argument is always a constant stringTôi không chắc chắn chính xác về ý nghĩa của bạn với điều đó.
Sebastian Mach

Như tôi đã nói, "He has %d cherries\n"là một chuỗi không đổi, có nghĩa là nó là một hằng số thời gian biên dịch. Tuy nhiên, để được công bằng, lời khuyên của tác giả không phải là "không vượt qua chuỗi liên tục như printfcủa đối số đầu tiên", nó đã "không vượt qua chuỗi mà không %như printfcủa đối số đầu tiên."
Steve Summit

literally from the C programming book of Genesis. Anyone deprecating that usage is being quite offensively heretical- bạn đã không thực sự đọc K&R trong những năm gần đây. Ngày nay, có rất nhiều lời khuyên và phong cách viết mã không chỉ bị phản đối mà còn đơn giản là thực tiễn tồi tệ.
Voo

@Voo: Chà, hãy nói rằng không phải mọi thứ được coi là thực hành xấu đều là thực hành xấu. (Lời khuyên "đừng bao giờ sử dụng intlò xo đơn giản ".)
Steve Summit

1
@Steve Tôi không biết bạn đã nghe câu đó ở đâu, nhưng đó chắc chắn không phải là kiểu thực hành tồi tệ (tệ hại?) Mà chúng ta đang nói đến ở đó. Đừng hiểu lầm tôi, vào thời điểm đó, mã hoàn toàn ổn, nhưng bạn thực sự không muốn nhìn vào k & r nhiều nhưng như một ghi chú lịch sử ngày nay. "Đó là trong k & r" không phải là một chỉ báo về chất lượng tốt ngày nay, đó là tất cả
Voo

9

Một khía cạnh khá khó chịu printflà ngay cả trên các nền tảng mà việc đọc bộ nhớ lạc chỉ có thể gây ra tác hại hạn chế (và có thể chấp nhận được), một trong các ký tự định dạng %n, khiến đối số tiếp theo được hiểu là một con trỏ đến một số nguyên có thể ghi và gây ra số lượng ký tự đầu ra cho đến nay sẽ được lưu trữ vào biến được xác định qua đó. Tôi chưa bao giờ tự mình sử dụng tính năng đó và đôi khi tôi sử dụng các phương pháp kiểu printf nhẹ mà tôi đã viết để chỉ bao gồm các tính năng mà tôi thực sự sử dụng (và không bao gồm tính năng đó hoặc bất cứ thứ gì tương tự) nhưng nhận được các chuỗi hàm printf tiêu chuẩn từ các nguồn không đáng tin cậy có thể để lộ các lỗ hổng bảo mật ngoài khả năng đọc lưu trữ tùy ý.


8

Vì không ai đề cập đến, tôi muốn thêm một ghi chú về hiệu suất của họ.

Trong các trường hợp bình thường, giả sử không có tối ưu hóa trình biên dịch nào được sử dụng (tức là printf()thực sự gọi printf()chứ không phải fputs()), tôi sẽ mong đợi printf()hoạt động kém hiệu quả hơn, đặc biệt là đối với các chuỗi dài. Điều này là do printf()phải phân tích cú pháp chuỗi để kiểm tra xem có bất kỳ ký hiệu chuyển đổi nào không.

Để xác nhận điều này, tôi đã chạy một số thử nghiệm. Thử nghiệm được thực hiện trên Ubuntu 14.04, với gcc 4.8.4. Máy của tôi sử dụng CPU Intel i5. Chương trình đang được thử nghiệm như sau:

#include <stdio.h>
int main() {
    int count = 10000000;
    while(count--) {
        // either
        printf("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM");
        // or
        fputs("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM", stdout);
    }
    fflush(stdout);
    return 0;
}

Cả hai đều được biên dịch với gcc -Wall -O0. Thời gian được đo bằng cách sử dụng time ./a.out > /dev/null. Sau đây là kết quả của một lần chạy điển hình (Tôi đã chạy chúng năm lần, tất cả các kết quả đều trong vòng 0,002 giây).

Đối với printf()biến thể:

real    0m0.416s
user    0m0.384s
sys     0m0.033s

Đối với fputs()biến thể:

real    0m0.297s
user    0m0.265s
sys     0m0.032s

Hiệu ứng này được khuếch đại nếu bạn có một chuỗi rất dài.

#include <stdio.h>
#define STR "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
#define STR2 STR STR
#define STR4 STR2 STR2
#define STR8 STR4 STR4
#define STR16 STR8 STR8
#define STR32 STR16 STR16
#define STR64 STR32 STR32
#define STR128 STR64 STR64
#define STR256 STR128 STR128
#define STR512 STR256 STR256
#define STR1024 STR512 STR512
int main() {
    int count = 10000000;
    while(count--) {
        // either
        printf(STR1024);
        // or
        fputs(STR1024, stdout);
    }
    fflush(stdout);
    return 0;
}

Đối với printf()biến thể (chạy ba lần, thực cộng / trừ 1,5 giây):

real    0m39.259s
user    0m34.445s
sys     0m4.839s

Đối với fputs()biến thể (chạy ba lần, thực cộng / trừ 0,2 giây):

real    0m12.726s
user    0m8.152s
sys     0m4.581s

Lưu ý: Sau khi kiểm tra assembly do gcc tạo ra, tôi nhận ra rằng gcc tối ưu hóa fputs()lệnh fwrite()gọi, ngay cả với -O0. (Lời printf()gọi vẫn không thay đổi.) Tôi không chắc liệu điều này có làm mất hiệu lực kiểm tra của tôi hay không, vì trình biên dịch tính toán độ dài chuỗi cho fwrite()tại thời điểm biên dịch.


2
Nó sẽ không làm mất hiệu lực thử nghiệm của bạn, như fputs()thường được sử dụng với các hằng chuỗi và rằng cơ hội tối ưu hóa là một phần của điểm bạn muốn make.This nói thêm một chạy thử nghiệm với một chuỗi dynamicly tạo ra với fputs()fprintf()sẽ là một điểm thoải mái dữ liệu bổ sung .
Patrick Schlüter

@ PatrickSchlüter Thử nghiệm với các chuỗi được tạo động dường như đánh bại mục đích của câu hỏi này ... OP dường như chỉ quan tâm đến các ký tự chuỗi được in.
user12205,

1
Anh ấy không nói rõ nó ngay cả khi ví dụ của anh ấy sử dụng chuỗi ký tự. Trên thực tế, tôi nghĩ rằng sự nhầm lẫn của anh ấy về lời khuyên của cuốn sách là kết quả của việc sử dụng các ký tự chuỗi trong ví dụ. Với chuỗi ký tự, lời khuyên về sách bằng cách nào đó là đáng ngờ, với chuỗi động thì đó là lời khuyên tốt.
Patrick Schlüter

1
/dev/nullđại loại là làm cho điều này trở thành một món đồ chơi, trong đó thường khi tạo đầu ra được định dạng, mục tiêu của bạn là đầu ra đi đâu đó, không bị loại bỏ. Sau khi bạn thêm thời gian "thực sự không loại bỏ dữ liệu", chúng sẽ so sánh như thế nào?
Yakk - Adam Nevraumont

7
printf("Hello World\n")

tự động biên dịch thành tương đương

puts("Hello World")

bạn có thể kiểm tra nó bằng cách tháo gỡ tệp thực thi của mình:

push rbp
mov rbp,rsp
mov edi,str.Helloworld!
call dword imp.puts
mov eax,0x0
pop rbp
ret

sử dụng

char *variable;
... 
printf(variable)

sẽ dẫn đến các vấn đề bảo mật, đừng bao giờ sử dụng printf theo cách đó!

vì vậy sách của bạn thực sự đúng, việc sử dụng printf với một biến không được dùng nữa nhưng bạn vẫn có thể sử dụng printf ("my string \ n") vì nó sẽ tự động trở thành giá


12
Hành vi này thực sự phụ thuộc hoàn toàn vào trình biên dịch.
Jabberwocky

6
Điều này gây hiểu lầm. Bạn nói A compiles to B, nhưng trong thực tế, bạn có nghĩa là A and B compile to C.
Sebastian Mach

6

Đối với gcc, có thể kích hoạt các cảnh báo cụ thể để kiểm tra printf()scanf().

Tài liệu gcc cho biết:

-Wformatđược bao gồm trong -Wall. Đối với kiểm soát nhiều hơn một số khía cạnh của định dạng kiểm tra, các tùy chọn -Wformat-y2k, -Wno-format-extra-args, -Wno-format-zero-length, -Wformat-nonliteral, -Wformat-security, và -Wformat=2có sẵn, nhưng không bao gồm trong -Wall.

Tính -Wformatnăng được bật trong -Walltùy chọn không kích hoạt một số cảnh báo đặc biệt giúp tìm ra các trường hợp sau:

  • -Wformat-nonliteral sẽ cảnh báo nếu bạn không chuyển một chuỗi nhỏ làm định dạng định dạng.
  • -Wformat-securitysẽ cảnh báo nếu bạn chuyển một chuỗi có thể chứa một cấu trúc nguy hiểm. Đó là một tập hợp con của -Wformat-nonliteral.

Tôi phải thừa nhận rằng việc kích hoạt -Wformat-securityđã tiết lộ một số lỗi mà chúng tôi gặp phải trong codebase của mình (mô-đun ghi nhật ký, mô-đun xử lý lỗi, mô-đun đầu ra xml, tất cả đều có một số chức năng có thể thực hiện những việc không xác định nếu chúng được gọi với ký tự% trong tham số của chúng. Để biết thông tin, cơ sở mã của chúng tôi hiện đã được khoảng 20 năm tuổi và ngay cả khi chúng tôi nhận thức được những vấn đề này, chúng tôi đã vô cùng ngạc nhiên khi kích hoạt các cảnh báo này vì có bao nhiêu lỗi trong số này vẫn còn trong cơ sở mã).


1

Bên cạnh các câu trả lời được giải thích rõ ràng khác với bất kỳ mối quan tâm phụ nào được đề cập, tôi muốn đưa ra câu trả lời chính xác và ngắn gọn cho câu hỏi đã cung cấp.


Tại sao printfvới một đối số (không có chỉ định chuyển đổi) không được dùng nữa?

Một printflời gọi hàm với đối số duy nhất nói chung là không phản đối và cũng không có lỗ hổng khi sử dụng đúng cách như bạn luôn phải viết mã.

C Người dùng trên toàn thế giới, từ người mới bắt đầu trạng thái đến chuyên gia trạng thái sử dụng printfcách đó để đưa ra một cụm từ văn bản đơn giản làm đầu ra cho bảng điều khiển.

Hơn nữa, Ai đó phải phân biệt xem đối số một và duy nhất này là một chuỗi ký tự hoặc một con trỏ đến một chuỗi, đối số này hợp lệ nhưng thường không được sử dụng. Tất nhiên, đối với phần sau, có thể xảy ra các kết quả đầu ra không thuận tiện hoặc bất kỳ loại Hành vi không xác định nào , khi con trỏ không được đặt đúng cách để trỏ đến một chuỗi hợp lệ nhưng những điều này cũng có thể xảy ra nếu các chỉ định định dạng không khớp với các đối số tương ứng bằng cách cho nhiều đối số.

Tất nhiên, cũng không đúng và thích hợp rằng chuỗi, được cung cấp như một đối số duy nhất, có bất kỳ định dạng hoặc thông số chuyển đổi nào, vì sẽ không có chuyển đổi nào xảy ra.

Điều đó nói rằng, đưa ra một chuỗi đơn giản giống "Hello World!"như đối số duy nhất mà không có bất kỳ mã định dạng nào bên trong chuỗi đó giống như bạn đã cung cấp trong câu hỏi:

printf("Hello World!");

hoàn toàn không bị phản đối hoặc " thực hành xấu " cũng như không có bất kỳ lỗ hổng nào.

Trên thực tế, nhiều lập trình viên C bắt đầu học và sử dụng C hoặc thậm chí các ngôn ngữ lập trình nói chung với chương trình HelloWorld đó và printfcâu lệnh này là câu lệnh đầu tiên của loại hình này.

Chúng sẽ không như vậy nếu chúng bị phản đối.

Trong một cuốn sách mà tôi đang đọc, nó được viết rằng printfvới một đối số duy nhất (không có chỉ định chuyển đổi) không được dùng nữa.

Vậy thì tôi sẽ tập trung vào cuốn sách hoặc tác giả. Theo quan điểm của tôi, nếu một tác giả thực sự đang làm như vậy, những khẳng định không chính xác và thậm chí giảng dạy điều đó mà không giải thích rõ ràng tại sao anh ấy / cô ấy lại làm như vậy (nếu những khẳng định đó thực sự tương đương theo nghĩa đen được cung cấp trong cuốn sách đó), tôi sẽ coi đó là một cuốn sách tồi . Một tốt cuốn sách, như trái ngược với điều đó, sẽ giải thích lý do tại sao để tránh những loại nhất định các phương pháp hoặc các chức năng lập trình.

Theo những gì tôi đã nói ở trên, việc sử dụng printfchỉ với một đối số (một chuỗi ký tự) và không có bất kỳ ký hiệu định dạng nào không bị phản đối hoặc bị coi là "hành vi xấu" .

Bạn nên hỏi tác giả, ý của anh ấy với điều đó hoặc tốt hơn nữa, hãy nhớ anh ấy để làm rõ hoặc sửa phần tương đối cho lần xuất bản tiếp theo hoặc các nhà xuất bản nói chung.


Bạn có thể thêm vào đó printf("Hello World!");không tương đương với puts("Hello World!");dù sao, mà nói điều gì đó về tác giả của đề nghị.
chqrlie
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.