Những hàm nào từ thư viện chuẩn phải (nên) tránh?


90

Tôi đã đọc trên Stack Overflow rằng một số hàm C là "lỗi thời" hoặc "nên tránh". Bạn có thể vui lòng cho tôi một số ví dụ về loại chức năng này và lý do tại sao?

Những lựa chọn thay thế cho những chức năng đó tồn tại?

Chúng ta có thể sử dụng chúng một cách an toàn - bất kỳ thực hành tốt nào không?


3
Tôi tin rằng đó là một điều rất tốt để biết. Một số hàm từ C thực sự nên tránh và chỉ sử dụng cho mục đích giáo dục.
INS

@Felix, sẽ dễ dàng hơn trong việc chỉnh sửa một câu trả lời duy nhất (được đánh dấu là đúng) trong tương lai. Nó thực sự được cập nhật liên quan đến các câu trả lời có thể sẽ thay đổi theo thời gian. Biết đâu, có thể Jeff sẽ cung cấp huy hiệu 'người gác cổng' cho những người luôn cập nhật câu trả lời trong vài năm tới.
Tim Post

1
@Tim Đăng: Ok tôi sẽ xóa bình luận của mình.
Felix Kling

3
Tôi sẽ không sử dụng strncpy()như một sự thay thế chung cho strcpy()và tôi sẽ không sử dụng strncat()bao giờ vì nó có giao diện không trực quan nhất có thể tưởng tượng được - BẠN có biết thông số độ dài chỉ định là gì không?
Jonathan Leffler

2
Sử dụng strncpy và strncat hầu như luôn luôn là một sai lầm. Chúng chắc chắn không nên được sử dụng thay cho strcpy và strcat !! scanf và sprintf cũng là hoàn toàn có thể sử dụng nếu bạn biết cách sử dụng chúng ...
R .. GitHub DỪNG GIÚP ICE

Câu trả lời:


58

Các hàm
không được sử dụng Không an toàn
Một ví dụ hoàn hảo về một hàm như vậy là hàm get () , bởi vì không có cách nào để cho biết bộ đệm đích lớn như thế nào. Do đó, bất kỳ chương trình nào đọc đầu vào bằng hàm get () đều có lỗ hổng tràn bộ đệm . Vì những lý do tương tự, người ta nên sử dụng strncpy () thay cho strcpy ()strncat () thay cho strcat () .

Tuy nhiên, một số ví dụ khác bao gồm hàm tmpfile ()mktemp () do các vấn đề bảo mật tiềm ẩn với việc ghi đè các tệp tạm thời và được thay thế bởi hàm mkstemp () an toàn hơn .

Non-Reentrant
Các ví dụ khác bao gồm gethostbyaddr ()gethostbyname () không phải là reentrant (và do đó, không được đảm bảo là an toàn cho luồng) và đã được thay thế bởi getaddrinfo ()freeaddrinfo () của reentrant .

Bạn có thể nhận thấy một mẫu ở đây ... thiếu bảo mật (có thể do không đưa đủ thông tin vào chữ ký để có thể triển khai nó một cách an toàn) hoặc không tham gia lại là những nguyên nhân phổ biến khiến chúng ta không thể sử dụng.

Lỗi thời, không di động
Một số chức năng khác đơn giản là không được dùng nữa vì chúng trùng lặp chức năng và không linh hoạt như các biến thể khác. Ví dụ: bzero () không được chấp nhận thay vì memset () .

An toàn
luồng và sự truy cập lại Bạn đã hỏi trong bài đăng của mình về an toàn luồng và sự truy cập lại. Có một sự khác biệt nhỏ. Một hàm được sử dụng lại nếu nó không sử dụng bất kỳ trạng thái có thể thay đổi, chia sẻ nào. Vì vậy, ví dụ: nếu tất cả thông tin nó cần được truyền vào hàm và bất kỳ vùng đệm nào cần thiết cũng được chuyển vào hàm (thay vì được chia sẻ bởi tất cả các lệnh gọi đến hàm), thì nó sẽ được đưa vào lại. Điều đó có nghĩa là các luồng khác nhau, bằng cách sử dụng các tham số độc lập, không có nguy cơ vô tình chia sẻ trạng thái. Reentrancy là sự đảm bảo mạnh mẽ hơn sự an toàn của luồng. Một hàm là an toàn cho luồng nếu nó có thể được nhiều luồng sử dụng đồng thời. Một chức năng là chuỗi an toàn nếu:

  • Nó được sử dụng lại (tức là nó không chia sẻ bất kỳ trạng thái nào giữa các cuộc gọi) hoặc:
  • Nó không thể tham gia lại, nhưng nó sử dụng đồng bộ hóa / khóa khi cần thiết cho trạng thái chia sẻ.

Nói chung, trong Đặc tả UNIX ĐơnIEEE 1003.1 (tức là "POSIX"), bất kỳ chức năng nào không được đảm bảo có thể đưa vào lại đều không được đảm bảo là an toàn cho luồng. Vì vậy, nói cách khác, chỉ những chức năng được đảm bảo là có thể nhập lại mới có thể được sử dụng trong các ứng dụng đa luồng (không có khóa bên ngoài). Tuy nhiên, điều đó không có nghĩa là việc triển khai các tiêu chuẩn này không thể chọn làm cho một chuỗi chức năng không sử dụng lại được an toàn. Ví dụ: Linux thường xuyên thêm đồng bộ hóa vào các chức năng không đăng nhập lại để thêm đảm bảo (ngoài Đặc điểm kỹ thuật UNIX duy nhất) về sự an toàn của luồng.

Chuỗi (và Bộ đệm bộ nhớ, nói chung)
Bạn cũng đã hỏi liệu có lỗ hổng cơ bản nào với chuỗi / mảng hay không. Một số người có thể tranh luận rằng đây là trường hợp, nhưng tôi sẽ tranh luận rằng không, không có sai sót cơ bản trong ngôn ngữ. C và C ++ yêu cầu bạn chuyển riêng độ dài / dung lượng của một mảng (nó không phải là thuộc tính ".length" như trong một số ngôn ngữ khác). Đây không phải là một thiếu sót, per-se. Bất kỳ nhà phát triển C và C ++ nào cũng có thể viết mã chính xác chỉ bằng cách chuyển độ dài làm tham số nếu cần. Vấn đề là một số API yêu cầu thông tin này không thể chỉ định nó làm tham số. Hoặc giả định rằng một số hằng số MAX_BUFFER_SIZE sẽ được sử dụng. Các API như vậy hiện đã không được dùng nữa và được thay thế bằng các API thay thế cho phép chỉ định kích thước mảng / bộ đệm / chuỗi.

Scanf (Trong câu trả lời cho câu hỏi cuối cùng của bạn)
Cá nhân tôi sử dụng thư viện C ++ iostreams (std :: cin, std :: cout, các toán tử << và >>, std :: getline, std :: istringstream, std :: ostringstream , v.v.), vì vậy tôi thường không giải quyết vấn đề đó. Tuy nhiên, nếu tôi buộc phải sử dụng C thuần túy, cá nhân tôi sẽ chỉ sử dụng fgetc () hoặc getchar () kết hợp với strtol () , strtoul () , v.v. và phân tích cú pháp thủ công, vì tôi không phải là một fan hâm mộ lớn của varargs hoặc chuỗi định dạng. Điều đó nói rằng, theo hiểu biết tốt nhất của tôi, không có vấn đề gì với [f] scanf () , [f] printf (), v.v., miễn là bạn tự tạo các chuỗi định dạng, bạn sẽ không bao giờ chuyển các chuỗi định dạng tùy ý hoặc cho phép sử dụng đầu vào của người dùng làm chuỗi định dạng và bạn sử dụng các macro định dạng được xác định trong <inttypes.h> khi thích hợp. (Lưu ý, snprintf () nên được sử dụng thay cho sprintf () , nhưng điều đó liên quan đến việc không chỉ định kích thước của bộ đệm đích chứ không phải việc sử dụng các chuỗi định dạng). Tôi cũng nên chỉ ra rằng, trong C ++, định dạng boost :: cung cấp định dạng giống printf mà không có varargs.


4
"Không được dùng" là một từ mạnh, có ý nghĩa cụ thể khi thảo luận về Tiêu chuẩn C ++. Theo nghĩa đó, get (), strcpy (), v.v. không bị phản đối.

4
Chỉ cần bạn phân biệt được giữa "tiêu chuẩn C không được chấp nhận", "không được chấp nhận bởi Michael Aaron Safyan" và "không được chấp nhận bởi một người hoặc những người không xác định hy vọng biết họ đang nói về điều gì [cần dẫn nguồn]". Câu hỏi về phong cách mã hóa ưa thích, không phải về tiêu chuẩn C, vì vậy hai câu hỏi thứ hai là phù hợp. Nhưng giống như Neil, tôi yêu cầu phải xem xét kỹ lưỡng trước khi nhận ra rằng câu nói của bạn không có ý định bao hàm ý nghĩa đầu tiên.
Steve Jessop

11
strncpynói chung cũng nên tránh. Nó không làm những gì hầu hết các lập trình viên cho là nó làm. Nó không đảm bảo kết thúc (dẫn đến vượt quá bộ đệm) và nó đệm các chuỗi ngắn hơn (có thể làm giảm hiệu suất trong một số trường hợp).
Adrian McCarthy

2
@Adrian: Tôi đồng ý với bạn - không strncpy()và thậm chí tệ hơn strncat()là một sự thay thế hợp lý cho các biến thể n-less.
Jonathan Leffler

4
Câu trả lời này lan truyền giáo điều vô nghĩa về việc "thay thế strcpy bằng strncpy - Tôi không biết tại sao nhưng Microsoft yêu cầu tôi làm như vậy." strncpy không bao giờ được dự định là một phiên bản an toàn của strcpy! Đối mặt với nó là không an toàn hơn nhiều. Xem Tại sao strlcpy và strlcat được coi là không an toàn? .
Lundin

24

Một lần nữa mọi người lặp lại, giống như câu thần chú, khẳng định ngớ ngẩn rằng phiên bản "n" của các hàm str là phiên bản an toàn.

Nếu đó là những gì họ dự định thì họ sẽ luôn vô hiệu kết thúc các chuỗi.

Phiên bản "n" của các hàm được viết để sử dụng với các trường có độ dài cố định (chẳng hạn như mục nhập thư mục trong các hệ thống tệp đầu tiên) trong đó dấu chấm dứt nul chỉ được yêu cầu nếu chuỗi không điền vào trường. Đây cũng là lý do tại sao các hàm có các tác dụng phụ kỳ lạ là không hiệu quả một cách vô nghĩa nếu chỉ được sử dụng làm thay thế - lấy ví dụ như strncpy ():

Nếu mảng được trỏ tới bởi s2 là một chuỗi ngắn hơn n byte, các byte rỗng sẽ được nối vào bản sao trong mảng được trỏ tới bởi s1, cho đến khi tất cả n byte được ghi.

Vì bộ đệm được phân bổ để xử lý tên tệp thường là 4kbyte, điều này có thể dẫn đến sự suy giảm lớn về hiệu suất.

Nếu bạn muốn các phiên bản an toàn "được cho là" thì hãy tải xuống - hoặc viết các quy trình strl của riêng bạn (strlcpy, strlcat, v.v.) luôn luôn kết thúc chuỗi và không có tác dụng phụ. Xin lưu ý rằng những điều này không thực sự an toàn vì chúng có thể âm thầm cắt ngắn chuỗi - đây hiếm khi là cách hành động tốt nhất trong bất kỳ chương trình thực tế nào. Có những trường hợp điều này là OK nhưng cũng có nhiều trường hợp có thể dẫn đến kết quả thảm khốc (ví dụ như in ra đơn thuốc).


1
Bạn đúng về strncpy(), nhưng sai về strncat(). strncat()không được thiết kế để sử dụng với các trường có độ dài cố định - nó thực sự được thiết kế để strcat()giới hạn số ký tự được nối. Khá dễ dàng để sử dụng nó như một "an toàn strcat()" bằng cách theo dõi không gian còn lại trong bộ đệm khi thực hiện nhiều phép nối và thậm chí dễ dàng hơn khi sử dụng nó như một "an toàn strcpy()" (bằng cách đặt ký tự đầu tiên của bộ đệm đích thành '\0'trước gọi nó). strncat() luôn kết thúc chuỗi đích và nó không ghi thêm '\0's.
caf

2
@caf - có nhưng strncat () hoàn toàn vô dụng vì nó coi độ dài tối đa được sao chép như một tham số - không phải độ dài của bộ đệm đích. Để ngăn chặn tràn bộ đệm, bạn cần biết độ dài của chuỗi đích hiện tại và nếu bạn biết lý do tại sao bạn lại sử dụng strncat () - phải tính lại độ dài đích - thay vì chỉ strlcat () chuỗi nguồn để cuối của chuỗi đích.
Que thăm dò

Điều đó vẫn không thay đổi thực tế là bạn ngụ ý rằng strncat()không phải lúc nào cũng kết thúc đích và nó được thiết kế để sử dụng với các trường có độ dài cố định, cả hai đều sai.
caf,

2
@chrisharris: strncat()sẽ hoạt động bình thường bất kể độ dài của chuỗi nguồn là bao nhiêu, trong khi strcat()thì không. Vấn đề strlcat()ở đây là nó không phải là một hàm C tiêu chuẩn.
David Thornley

@caf - bạn nói đúng về strncat (). Tôi chưa bao giờ thực sự sử dụng nó bởi vì - như tôi đã chỉ ra ở trên - nó hoàn toàn vô dụng. Do đó vẫn nên tránh.
Que thăm

19

Một số câu trả lời ở đây đề nghị sử dụng strncat()over strcat(); Tôi đề nghị rằng strncat()(và strncpy()) cũng nên tránh. Nó có các vấn đề gây khó khăn cho việc sử dụng đúng cách và dẫn đến lỗi:

  • tham số độ dài strncat()liên quan đến (nhưng không hoàn toàn chính xác - xem điểm thứ 3) số ký tự tối đa có thể được sao chép vào đích thay vì kích thước của bộ đệm đích. Điều này làm cho strncat()việc sử dụng trở nên khó khăn hơn bình thường, đặc biệt nếu nhiều mục sẽ được nối với đích.
  • có thể khó xác định xem kết quả có bị cắt bớt hay không (điều này có thể quan trọng hoặc không)
  • rất dễ xảy ra lỗi từng cái một. Như tiêu chuẩn C99 lưu ý, "Do đó, số ký tự tối đa có thể kết thúc trong mảng được trỏ tới s1strlen(s1)+n+1" cho một lệnh gọi giống nhưstrncat( s1, s2, n)

strncpy()cũng có một vấn đề có thể dẫn đến lỗi mà bạn cố gắng sử dụng nó theo cách trực quan - nó không đảm bảo rằng đích đến là rỗng. Để đảm bảo rằng bạn phải đảm bảo rằng bạn xử lý cụ thể trường hợp góc đó bằng cách tự mình thả '\0'vào vị trí cuối cùng của bộ đệm (ít nhất là trong một số tình huống nhất định).

Tôi khuyên bạn nên sử dụng một cái gì đó giống như OpenBSD strlcat()strlcpy()(mặc dù tôi biết rằng một số người không thích những chức năng đó; tôi tin rằng chúng dễ sử dụng một cách an toàn hơn nhiều so với strncat()/ strncpy()).

Dưới đây là một chút những gì Todd Miller và Theo de Raadt phải nói về các vấn đề strncat()strncpy():

Có một số vấn đề gặp phải khi strncpy()strncat()được sử dụng làm phiên bản an toàn của strcpy()strcat(). Cả hai hàm đều xử lý kết thúc NUL và tham số độ dài theo những cách khác nhau và không trực quan, gây nhầm lẫn ngay cả những lập trình viên có kinh nghiệm. Chúng cũng không cung cấp cách dễ dàng để phát hiện khi nào xảy ra cắt ngắn. ... Trong tất cả các vấn đề này, sự nhầm lẫn do các tham số chiều dài và vấn đề liên quan đến đầu cuối NUL là quan trọng nhất. Khi chúng tôi kiểm tra cây nguồn OpenBSD để tìm các lỗ hổng bảo mật tiềm ẩn, chúng tôi đã phát hiện thấy việc lạm dụng tràn lan strncpy()strncat() . Mặc dù không phải tất cả những điều này đều dẫn đến các lỗ hổng bảo mật có thể khai thác, nhưng họ đã làm rõ rằng các quy tắc sử dụng strncpy()strncat()trong các hoạt động chuỗi an toàn bị hiểu nhầm rộng rãi.

Kiểm tra bảo mật của OpenBSD phát hiện ra rằng các lỗi với các chức năng này đang "tràn lan". Không giống như gets(), các chức năng này có thể được sử dụng một cách an toàn, nhưng trong thực tế có rất nhiều vấn đề do giao diện khó hiểu, không trực quan và khó sử dụng chính xác. Tôi biết rằng Microsoft cũng đã thực hiện phân tích (mặc dù tôi không biết họ có thể đã xuất bản bao nhiêu dữ liệu của họ), và kết quả là đã cấm (hoặc ít nhất là rất khuyến khích - 'lệnh cấm' có thể không tuyệt đối) sử dụng strncat()strncpy()(trong số các chức năng khác).

Một số liên kết với nhiều thông tin hơn:


1
Tốt hơn hết là bạn nên theo dõi độ dài của các chuỗi bên ngoài chính chuỗi đó. Tương tự như vậy, việc nối hai chuỗi (-with-length) một cách an toàn trở thành vấn đề của một phép tính đơn giản (để có được kích thước bộ đệm cần thiết) một khả năng phân bổ lại, và a memmove(). (Chà, bạn có thể sử dụng memcpy()trong trường hợp bình thường khi các chuỗi độc lập.)
Donal Fellows

1
Điểm thứ hai của bạn là sai - strncat() luôn kết thúc chuỗi đích.
caf,

1
Điểm thứ hai của bạn là sai cho strncat(). Tuy nhiên, nó là chính xác cho strncpy(), mà có một số vấn đề khác. strncat()là một sự thay thế hợp lý cho strcat(), nhưng strncpy()không phải là một sự thay thế hợp lý cho strcpy().
David Thornley

2
caf và David đúng 100% về tuyên bố của tôi strncat()không phải lúc nào cũng vô hiệu. Tôi đã nhầm lẫn giữa các hành vi của strncat()strncpy()một chút (một lý do khác mà chúng có chức năng tránh - chúng có tên ngụ ý các hành vi tương tự, nhưng trên thực tế lại hành xử khác nhau theo những cách quan trọng ...). Tôi đã sửa đổi câu trả lời của mình để sửa điều này cũng như thêm thông tin bổ sung.
Michael Burr

1
Lưu ý rằng đó char str[N] = ""; strncat(str, "long string", sizeof(str));là tràn bộ đệm nếu N không đủ lớn. Các strncat()chức năng là quá dễ dàng để lạm dụng; nó không nên được sử dụng. Nếu bạn có thể sử dụng strncat()một cách an toàn, bạn có thể đã sử dụng memmove()hoặc memcpy()thay thế (và những thứ đó sẽ hiệu quả hơn).
Jonathan Leffler

9

Các hàm thư viện chuẩn không bao giờ được sử dụng:

setjmp.h

  • setjmp(). Cùng với đó longjmp(), các hàm này được hiểu rộng rãi là cực kỳ nguy hiểm khi sử dụng: chúng dẫn đến lập trình mì Ý, chúng đi kèm với nhiều dạng hành vi không xác định, chúng có thể gây ra các tác dụng phụ không mong muốn trong môi trường chương trình, chẳng hạn như ảnh hưởng đến các giá trị được lưu trữ trên ngăn xếp. Tài liệu tham khảo: MISRA-C: 2012 quy tắc 21.4, CERT C MSC22-C .
  • longjmp(). Thấy chưa setjmp().

stdio.h

  • gets(). Hàm đã bị xóa khỏi ngôn ngữ C (theo C11), vì nó không an toàn theo thiết kế. Hàm đã được gắn cờ là lỗi thời trong C99. Sử dụng fgets()thay thế. Tài liệu tham khảo: ISO 9899: 2011 K.3.5.4.1, xem chú thích 404.

stdlib.h

  • atoi()họ các chức năng. Chúng không có xử lý lỗi nhưng gọi hành vi không xác định bất cứ khi nào lỗi xảy ra. Các chức năng thừa hoàn toàn có thể được thay thế bằng strtol()họ các chức năng. Tài liệu tham khảo: MISRA-C: 2012 quy tắc 21.7.

string.h

  • strncat(). Có một giao diện khó xử thường bị sử dụng sai. Nó chủ yếu là một chức năng thừa. Cũng xem nhận xét cho strncpy().
  • strncpy(). Mục đích của chức năng này không bao giờ là một phiên bản an toàn hơn của strcpy(). Mục đích duy nhất của nó là luôn xử lý một định dạng chuỗi cổ trên hệ thống Unix, và việc nó được đưa vào thư viện chuẩn là một sai lầm đã biết. Hàm này nguy hiểm vì nó có thể để lại chuỗi mà không có kết thúc null và các lập trình viên thường sử dụng nó không đúng cách. Tài liệu tham khảo: Tại sao strlcpy và strlcat được coi là không an toàn? .

Các chức năng thư viện tiêu chuẩn nên được sử dụng thận trọng:

khẳng định.h

  • assert(). Đi kèm với chi phí chung và thường không được sử dụng trong mã sản xuất. Tốt hơn là sử dụng trình xử lý lỗi dành riêng cho ứng dụng hiển thị lỗi nhưng không nhất thiết phải đóng toàn bộ chương trình.

signal.h

  • signal(). Tài liệu tham khảo: MISRA-C: 2012 quy tắc 21.5, CERT C SIG32-C .

stdarg.h

  • va_arg()họ các chức năng. Sự hiện diện của các hàm có độ dài thay đổi trong chương trình C hầu như luôn luôn là dấu hiệu cho thấy thiết kế chương trình kém. Nên tránh trừ khi bạn có yêu cầu rất cụ thể.

stdio.h
Nói chung, toàn bộ thư viện này không được khuyến nghị cho mã sản xuất , vì nó đi kèm với nhiều trường hợp hành vi được xác định kém và loại an toàn kém.

  • fflush(). Hoàn toàn tốt để sử dụng cho các luồng đầu ra. Gọi hành vi không xác định nếu được sử dụng cho các luồng đầu vào.
  • gets_s(). Phiên bản an toàn của gets()bao gồm trong giao diện kiểm tra giới hạn C11. Nó được ưu tiên sử dụng fgets()thay thế, theo khuyến nghị tiêu chuẩn C. Tài liệu tham khảo: ISO 9899: 2011 K.3.5.4.1.
  • printf()họ các chức năng. Các hàm nặng về tài nguyên đi kèm với nhiều hành vi không xác định và độ an toàn kiểu kém. sprintf()cũng có lỗ hổng. Những chức năng này nên được tránh trong mã sản xuất. Tài liệu tham khảo: MISRA-C: 2012 quy tắc 21.6.
  • scanf()họ các chức năng. Xem nhận xét về printf(). Ngoài ra, - scanf()dễ bị tràn bộ đệm nếu không được sử dụng đúng cách. fgets()được ưu tiên sử dụng khi có thể. Tài liệu tham khảo: CERT C INT05-C , MISRA-C: 2012 quy tắc 21.6.
  • tmpfile()họ các chức năng. Đi kèm với các vấn đề về lỗ hổng bảo mật khác nhau. Tài liệu tham khảo: CERT C FIO21-C .

stdlib.h

  • malloc()họ các chức năng. Hoàn toàn tốt để sử dụng trong các hệ thống được lưu trữ, mặc dù hãy lưu ý các vấn đề nổi tiếng trong C90 và do đó không tạo ra kết quả . Họ malloc()các hàm không bao giờ được sử dụng trong các ứng dụng tự do. Tài liệu tham khảo: MISRA-C: 2012 quy tắc 21.3.

    Cũng lưu ý rằng điều đó realloc()rất nguy hiểm trong trường hợp bạn ghi đè con trỏ cũ với kết quả là realloc(). Trong trường hợp chức năng không thành công, bạn tạo ra một rò rỉ.

  • system(). Đi kèm với rất nhiều chi phí và mặc dù có tính di động, thay vào đó, tốt hơn là sử dụng các hàm API dành riêng cho hệ thống. Đi kèm với nhiều hành vi được xác định kém khác nhau. Tài liệu tham khảo: CERT C ENV33-C .

string.h

  • strcat(). Xem nhận xét cho strcpy().
  • strcpy(). Hoàn toàn tốt để sử dụng, trừ khi kích thước của dữ liệu được sao chép không xác định hoặc lớn hơn bộ đệm đích. Nếu không kiểm tra kích thước dữ liệu đến được thực hiện, có thể có vượt quá bộ đệm. Đó không phải là lỗi của strcpy()bản thân mà là do ứng dụng gọi điện - ứng dụng strcpy()không an toàn đó chủ yếu là một huyền thoại do Microsoft tạo ra .
  • strtok(). Thay đổi chuỗi người gọi và sử dụng các biến trạng thái nội bộ, điều này có thể làm cho nó không an toàn trong môi trường đa luồng.

Tôi đề nghị giới thiệu static_assert()thay thế assert(), nếu điều kiện có thể được giải quyết tại thời điểm biên dịch. Ngoài ra, sprintf()hầu như luôn luôn có thể được thay thế snprintf()mà an toàn hơn một chút.
user694733

1
Sử dụng strtok()trong một hàm A có nghĩa là (a) hàm không thể gọi bất kỳ hàm nào khác cũng sử dụng strtok()trong khi A đang sử dụng nó và (b) có nghĩa là không hàm nào gọi A có thể được sử dụng strtok()khi nó gọi A. Nói cách khác, sử dụng strtok()đầu độc chuỗi cuộc gọi; nó không thể được sử dụng một cách an toàn trong mã thư viện vì nó phải ghi lại rằng nó sử dụng strtok()để ngăn người dùng khác strtok()gọi mã thư viện.
Jonathan Leffler

Các strncpylà chức năng thích hợp để sử dụng khi viết một zero- đệm đệm chuỗi với dữ liệu lấy từ hoặc là một chuỗi zero-chấm dứt hoặc một bộ đệm zero-đệm có kích thước ít nhất là lớn như đích. Bộ đệm không đệm không quá phổ biến, nhưng chúng cũng không rõ ràng.
supercat

7

Một số người cho rằng strcpystrcatnên tránh, ủng hộ strncpystrncat. Điều này là hơi chủ quan, theo ý kiến ​​của tôi.

Chúng chắc chắn nên được tránh khi xử lý đầu vào của người dùng - không có nghi ngờ gì ở đây.

Trong mã "xa" với người dùng, khi bạn chỉ biết bộ đệm đủ dài strcpystrcatcó thể hiệu quả hơn một chút vì việc tính toán nchuyển cho anh em họ của họ có thể là thừa.


4
Và nếu có sẵn, tốt hơn hết strlcatstrlcpyvì phiên bản 'n' không đảm bảo kết thúc NULL của chuỗi đích.
Dan Andreatta

Tôi nghe có vẻ như tối ưu hóa quá sớm. Nhưng IIRC, strncpy()sẽ viết chính xác từng nbyte, sử dụng ký tự nul nếu cần thiết. Như Dan, sử dụng phiên bản an toàn là lựa chọn tốt nhất IMO.
Bastien Léonard

@Dan, rất tiếc những chức năng này không phải là tiêu chuẩn và do đó không thuộc về cuộc thảo luận này
Eli Bendersky

@Eli: nhưng thực tế là strncat()có thể khó sử dụng một cách chính xác và an toàn (và nên tránh) là chủ đề.
Michael Burr

@Michael: Tôi sẽ không nói rằng nó khó sử dụng một cách chính xác và an toàn. Với sự chăm sóc, nó hoạt động tốt
Eli Bendersky

6

Tránh

  • strtok cho các chương trình đa luồng vì nó không an toàn cho luồng.
  • gets vì nó có thể gây tràn bộ đệm

2
Chúng là những trường hợp hơi khác nhau. Bạn có thể sử dụng strtok một cách an toàn nếu bạn biết chương trình của mình không đa luồng hoặc bằng cách nào đó bạn khóa quyền truy cập vào nó, nhưng bạn không thể sử dụng an toàn, vì vậy đừng bao giờ sử dụng nó.
jcoder

9
Vấn đề với strtok()một chút vượt ra ngoài sự an toàn của luồng - nó không an toàn ngay cả trong một chương trình luồng đơn trừ khi bạn biết chắc chắn rằng không có hàm nào mà mã của bạn có thể gọi trong khi sử dụng strtok()cũng không sử dụng nó (hoặc chúng sẽ làm rối tung strtok()trạng thái đúng không từ dưới bạn). Trên thực tế, hầu hết các trình biên dịch nhắm mục tiêu đến các nền tảng đa luồng xử lý strtok()các vấn đề tiềm ẩn của các luồng khi các luồng đi bằng cách sử dụng lưu trữ cục bộ luồng cho strtok()dữ liệu tĩnh của. Nhưng điều đó vẫn không khắc phục được sự cố của các chức năng khác đang sử dụng nó khi bạn đang sử dụng (trong cùng một chủ đề).
Michael Burr

Thật vậy và tôi sẽ im lặng ngay bây giờ vì tôi không muốn khuyến khích bất cứ ai sử dụng strtok vì nó có nhiều vấn đề. Tôi chỉ muốn chỉ ra rằng nó khác với được mặc dù là nó có thể sử dụng nó một cách an toàn trong khi nó không thể sử dụng được một cách an toàn.
jcoder

@Jimmy: Thường có các phần mở rộng thư viện không chuẩn, hoặc bạn có thể tự viết. Các vấn đề lớn với strtok()là nó giữ một cách chính xác một bộ đệm để làm việc trong, vì vậy thay thế tốt nhất yêu cầu giữ một giá trị đệm xung quanh và đi qua nó trong.
David Thornley

strcspnthực hiện hầu hết những gì bạn cần - tìm dấu tách mã thông báo tiếp theo. Bạn có thể thực hiện lại một biến thể lành mạnh của strtoknó.
bluss

5

Có lẽ cần phải bổ sung một lần nữa, đó strncpy()không phải là sự thay thế có mục đích chung chostrcpy() cái tên của nó có thể gợi ý. Nó được thiết kế cho các trường có độ dài cố định không cần dấu chấm dứt nul (ban đầu nó được thiết kế để sử dụng với các mục nhập thư mục UNIX, nhưng có thể hữu ích cho những thứ như trường khóa mã hóa).

Tuy nhiên, nó rất dễ dàng để sử dụng strncat()thay thế cho strcpy():

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

(Bài ifkiểm tra rõ ràng có thể bị bỏ trong trường hợp phổ biến, nơi bạn biết rằng đó dest_sizechắc chắn là nonzero).


5

Ngoài ra, hãy kiểm tra danh sách các API bị cấm của Microsoft . Đây là những API (bao gồm nhiều API đã được liệt kê ở đây) bị cấm sử dụng mã Microsoft vì chúng thường bị lạm dụng và dẫn đến các vấn đề bảo mật.

Bạn có thể không đồng ý với tất cả chúng, nhưng chúng đều đáng xem xét. Họ thêm một API vào danh sách khi việc lạm dụng nó đã dẫn đến một số lỗi bảo mật.


2

Hầu như bất kỳ hàm nào xử lý các chuỗi kết thúc NUL đều có khả năng không an toàn. Nếu bạn đang nhận dữ liệu từ thế giới bên ngoài và thao tác với nó thông qua các hàm str * () thì bạn đã tự thiết lập cho mình một thảm họa


2

Đừng quên về sprintf - nó là nguyên nhân của nhiều vấn đề. Điều này đúng vì giải pháp thay thế, snprintf đôi khi có các cách triển khai khác nhau có thể khiến mã của bạn không thể di chuyển được.

  1. linux: http://linux.die.net/man/3/snprintf

  2. windows: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

Trong trường hợp 1 (linux) giá trị trả về là lượng dữ liệu cần thiết để lưu trữ toàn bộ bộ đệm (nếu nó nhỏ hơn kích thước của bộ đệm đã cho thì kết quả đầu ra đã bị cắt bớt)

Trong trường hợp 2 (cửa sổ), giá trị trả về là số âm trong trường hợp đầu ra bị cắt bớt.

Nói chung, bạn nên tránh các chức năng không phải là:

  1. đệm tràn bộ đệm an toàn (rất nhiều chức năng đã được đề cập ở đây)

  2. luồng an toàn / không đăng nhập lại (ví dụ: strtok)

Trong hướng dẫn sử dụng của từng chức năng, bạn nên tìm kiếm các từ khóa như: safe, sync, async, thread, buffer, bug


2
_sprintf()được tạo ra bởi Microsoft trước khi tiêu chuẩn snprintf()IIRC ra đời. ´StringCbPrintf () ´ khá giống với snprintf()mặc dù.
Bastien Léonard

bạn có thể sử dụng sprintfbằng cách nào đó một cách an toàn trong một số trường hợp: sprintf(buffer,"%10s",input);giới hạn nb sao chép byte đến 10 (nếu bufferchar buffer[11]nó an toàn ngay cả khi dữ liệu có thể gió lên cắt ngắn.
Jean-François Fabre

2

Rất khó để sử dụng scanfmột cách an toàn. Sử dụng tốt scanfcó thể tránh bị tràn bộ đệm, nhưng bạn vẫn dễ mắc phải hành vi không xác định khi đọc các số không phù hợp với kiểu được yêu cầu. Trong hầu hết các trường hợp, fgetssau đó tự phân tích cú pháp (sử dụng sscanf, strchrvv) là một lựa chọn tốt hơn.

Nhưng tôi sẽ không nói "tránh scanfmọi lúc". scanfcó công dụng của nó. Ví dụ, giả sử bạn muốn đọc thông tin nhập của người dùng trong một charmảng dài 10 byte. Bạn muốn xóa dòng mới ở cuối, nếu có. Nếu người dùng nhập nhiều hơn 9 ký tự trước dòng mới, bạn muốn lưu trữ 9 ký tự đầu tiên trong bộ đệm và loại bỏ mọi thứ cho đến dòng mới tiếp theo. Bạn có thể làm:

char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();

Khi bạn đã quen với thành ngữ này, nó ngắn hơn và theo một số cách gọn gàng hơn:

char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
    char *nl;
    if ((nl = strrchr(buf, '\n')) == NULL) {
        int c;
        while ((c = getchar()) != EOF && c != '\n') {
            ;
        }
    } else {
        *nl = 0;
    }
}

0

Trong tất cả các kịch bản sao chép / di chuyển chuỗi - strcat (), strncat (), strcpy (), strncpy (), v.v. - mọi thứ trở nên tốt hơn nhiều ( an toàn hơn ) nếu một vài phương pháp heuristics đơn giản được thực thi:

   1. Luôn NUL-fill (các) bộ đệm của bạn trước khi thêm dữ liệu.
   2. Khai báo bộ đệm ký tự là [SIZE + 1], với một hằng số macro.

Ví dụ, đã cho:

#define   BUFSIZE   10
char      Buffer[BUFSIZE+1] = { 0x00 };  /* The compiler NUL-fills the rest */

chúng ta có thể sử dụng mã như:

memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");

tương đối an toàn. Memset () sẽ xuất hiện trước strncpy (), ngay cả khi chúng tôi đã khởi tạo Bộ đệm tại thời điểm biên dịch, vì chúng tôi không biết mã rác nào khác được đặt vào nó trước khi hàm của chúng tôi được gọi. Strncpy () sẽ cắt bớt dữ liệu được sao chép thành "1234567890", và sẽ không kết thúc nó. Tuy nhiên, vì chúng tôi đã lấp đầy NUL toàn bộ bộ đệm - sizeof (Bộ đệm), chứ không phải là BUFSIZE - nên dù sao thì chúng tôi vẫn đảm bảo là một "ngoài phạm vi" cuối cùng chấm dứt NUL, miễn là chúng tôi hạn chế việc ghi của mình bằng BUFSIZE không đổi, thay vì sizeof (Bộ đệm).

Bộ đệm và BUFSIZE cũng hoạt động tốt cho snprintf ():

memset(Buffer,0x00,sizeof(Buffer));
if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) {
    /* Do some error-handling */
}   /* If using MFC, you need if(... < 0), instead */

Mặc dù snprintf () đặc biệt chỉ viết các ký tự BUFIZE-1, theo sau là NUL, điều này hoạt động an toàn. Vì vậy, chúng tôi "lãng phí" một byte NUL không liên quan ở cuối Bộ đệm ... chúng tôi ngăn chặn cả điều kiện tràn bộ đệm và chuỗi không kết thúc, với chi phí bộ nhớ khá nhỏ.

Cuộc gọi của tôi trên strcat () và strncat () khó hơn: không sử dụng chúng. Rất khó để sử dụng strcat () một cách an toàn và API cho strncat () phản trực quan đến mức nỗ lực cần thiết để sử dụng nó đúng cách sẽ phủ nhận bất kỳ lợi ích nào. Tôi đề xuất thả vào sau:

#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)

Thật hấp dẫn để tạo một trình đơn strcat (), nhưng không phải là một ý kiến ​​hay:

#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)

bởi vì target có thể là một con trỏ (do đó sizeof () không trả về thông tin chúng ta cần). Tôi không có giải pháp "phổ quát" tốt cho các trường hợp strcat () trong mã của bạn.

Một vấn đề mà tôi thường xuyên gặp phải từ các lập trình viên "strFunc () - Recognition" là cố gắng bảo vệ chống tràn bộ đệm bằng cách sử dụng strlen (). Điều này là tốt nếu các nội dung được đảm bảo là kết thúc NUL. Nếu không, bản thân strlen () có thể gây ra lỗi chạy quá bộ đệm (thường dẫn đến vi phạm phân đoạn hoặc tình huống kết xuất lõi khác), trước khi bạn tiếp cận mã "có vấn đề" mà bạn đang cố gắng bảo vệ.


-2

atoi không phải là chủ đề an toàn. Tôi sử dụng strtol thay thế, theo đề xuất từ ​​trang người đàn ông.


5
Điều này có vẻ như nó áp dụng cho một triển khai cụ thể. Không có lý do gì tại sao strtol()lại an toàn cho luồng mà lại không an toàn atoi().
David Thornley

2
Khuyến nghị sử dụng strtol không liên quan gì đến an toàn luồng mà là xử lý lỗi. Không chắc bạn lấy thông tin đó từ trang người đàn ông nào - tôi không thể tìm thấy bất kỳ đề xuất nào bên dưới man atoi(mặc dù vậy nên có).
Lundin
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.