Snprintf () LUÔN LUÔN null kết thúc?


82

Snprintf có luôn null kết thúc bộ đệm đích không?

Nói cách khác, điều này có đủ:

char dst[10];

snprintf(dst, sizeof (dst), "blah %s", somestr);

hoặc bạn phải làm như thế này, nếu somestr là đủ dài?

char dst[10];

somestr[sizeof (dst) - 1] = '\0';
snprintf(dst, sizeof (dst) - 1, "blah %s", somestr);

Tôi quan tâm đến cả những gì tiêu chuẩn nói và những gì một số libc phổ biến có thể làm mà không phải là hành vi tiêu chuẩn.


Bạn có nghĩa là nul chấm dứt somestr hoặc dst trong ví dụ thứ hai?
Hudson

@chux, Martin Ba đã đề cập đến điều đó trong câu trả lời được chấp nhận. :)
Giáo sư Falken

@chux Tôi nghĩ nó là tốt, bình luận của bạn chỉ nói rất rõ ràng rằng nếu tôi 0 dài, không có gì được viết. Tôi coi mọi nhận xét như một cái cớ tiềm năng để trò chuyện với những người bạn đồng hành cùng chồng. :)
Giáo sư Falken

@Prof. Falken Đồng ý rằng nhận xét là OK và rõ ràng, nhưng nó là thừa với các câu trả lời - chỉ bỏ lỡ điều đó trong bài đánh giá của tôi.
chux - Phục hồi Monica

stackoverflow.com/a/8712996/193892 Visual Studio hiện hỗ trợ snprintf ()
GS Falken

Câu trả lời:


71

Như câu trả lời khác lập: Nó nên :

snprintf... Ghi kết quả vào bộ đệm chuỗi ký tự. (...) sẽ được kết thúc bằng ký tự rỗng, trừ khi buf_size bằng 0.

Vì vậy, tất cả những gì bạn phải quan tâm là bạn không chuyển một vùng đệm có kích thước bằng không vào nó, bởi vì (rõ ràng) nó không thể ghi số 0 vào "hư không".


Tuy nhiên, hãy cẩn thận rằng thư viện của Microsoft không có một chức năng được gọi snprintfmà thay vào đó trong lịch sử chỉ có một chức năng được gọi là _snprintf(ghi chú dấu gạch dưới ở đầu) mà không thêm giá trị kết thúc. Đây là tài liệu (VS 2012, ~~ VS 2013):

http://msdn.microsoft.com/en-us/library/2ts7cx93%28v=vs.110%29.aspx

Giá trị trả lại

Gọi len là độ dài của chuỗi dữ liệu được định dạng (không bao gồm giá trị null kết thúc). len và số đếm được tính bằng byte cho _snprintf, các ký tự rộng cho _snwprintf.

  • Nếu len <count, thì các ký tự len được lưu trong bộ đệm, một dấu chấm dứt giá trị null được thêm vào và len được trả về.

  • Nếu len = count, thì các ký tự len được lưu trữ trong bộ đệm, không có dấu chấm dứt giá trị nào được thêm vào và len được trả về.

  • Nếu len> count, thì các ký tự đếm được lưu trong bộ đệm, không có dấu chấm dứt giá trị nào được thêm vào và giá trị âm được trả về.

(...)

Visual Studio 2015 (VC14) dường như đã giới thiệu phù hợp với snprintfchức năng, nhưng một trong những di sản với gạch hàng đầu và các phi hành vi null-chấm dứt vẫn còn đó:

Các snprintfchức năng truncates đầu ra khi len là lớn hơn hoặc bằng để đếm, bằng cách đặt một null-terminator tại buffer[count-1]. (...)

Đối với tất cả các chức năng khác hơn snprintf, nếu len = đếm, ký tự len được lưu trữ trong bộ đệm, không null-terminator được nối , (...)


22
Nhân danh Aslan, các kỹ sư của Microsoft đã nghĩ gì khi họ giới thiệu tính năng _snprintfnày lặng lẽ loại bỏ một tính năng an toàn quan trọngsnprintf và cho phép chuỗi không bị kết thúc bằng null ?!
Colin D Bennett

2
@ColinDBennett - đó là kỳ lạ và hùng mạnh gây phiền nhiễu và tôi không có đầu mối nếu có ai nghĩ gì cả :-)
Martin Ba

2
@MartinBa vâng, xin lỗi, những gì tôi đã thử nghiệm template <size_t size> int _snprintf_s(char (&buffer)[size], size_t count, const char *format [, argument] ...);và tôi cũng nên đề cập rằng điều này chỉ xảy ra với cờ biên dịch / GS (Security Check). Hàm đó biết kích thước, số đếm và chiều dài.
sekmet64

3
Ghi chú rằng mingw64 sử dụng (sử dụng?) Thực hiện _snprintf microsoft như snprintf "bình thường" trừ khi có quy định khác nvd.nist.gov/vuln/detail/CVE-2018-1000101
domenukk

2
@Sajjon Đó là một câu cảm thán bực tức ngớ ngẩn (và có lẽ hoàn toàn nguyên bản) ( idioms.thefreedictionary.com/in+the+name+of+God ) có lẽ hơi giống như một lời thề nhỏ ( en.wikipedia.org/wiki/Minced_oath ). Một ví dụ khác có thể là "Nhân danh Zeus ...?!" ( forum.wordreference.com/threads/in-the-name-of-zeus.2132965 )
Colin D Bennett

19

Theo snprintf (3) manpage.

Các hàm snprintf()vsnprintf()ghi tối đa sizebyte (bao gồm byte rỗng ở cuối ('\ 0')) vào str.

Vì vậy, có, không cần phải kết thúc nếu kích thước> = 1.


3
Và cảm ơn Chúa vì điều đó; đây là thiết kế hợp lý duy nhất. Toàn bộ điểm của các phiên bản đã kiểm tra của các chức năng này là phải an toàn , và sẽ thật tồi tệ nếu bạn phải thực hiện tất cả các lỗi kết thúc bằng tay.
Kerrek SB

1
Tôi khuyên bạn nên thử nghiệm nó trên (các) nền tảng bạn đang sử dụng trước khi dựa vào điều này. Ngay cả khi nó phải ghi byte null, tôi biết rằng tôi đã gặp phải các triển khai không có (nó có thể đã xảy ra với MinGW, sử dụng thời gian chạy MS cũ hơn).
Dmitri

10

Theo tiêu chuẩn C, trừ khi kích thước bộ đệm bằng 0 vsnprintf()snprintf()null chấm dứt đầu ra của nó.

Các snprintf()chức năng sẽ tương đương với sprintf(), với việc bổ sung các tham số n trong đó nêu kích thước của bộ đệm được gọi bằng s. Nếu n bằng 0, không có gì được viết và s có thể là một con trỏ rỗng. Nếu không, các byte đầu ra ngoài n-1 sẽ bị loại bỏ thay vì được ghi vào mảng và một byte rỗng được ghi vào cuối các byte thực sự được ghi vào mảng.

Vì vậy, nếu bạn cần biết bộ đệm lớn như thế nào để cấp phát, hãy sử dụng kích thước bằng 0 và sau đó bạn có thể sử dụng con trỏ null làm đích. Lưu ý rằng tôi đã liên kết đến các trang POSIX, nhưng những trang này nói rõ rằng không có ý định có bất kỳ sự khác biệt nào giữa Chuẩn C và POSIX khi chúng bao gồm cùng một mặt bằng:

Chức năng được mô tả trên trang tham chiếu này phù hợp với tiêu chuẩn ISO C. Bất kỳ xung đột nào giữa các yêu cầu được mô tả ở đây và tiêu chuẩn ISO C là không cố ý. Khối lượng POSIX.1-2008 này tuân theo tiêu chuẩn ISO C.

Hãy cảnh giác với phiên bản Microsoft của vsnprintf(). Nó chắc chắn hoạt động khác với phiên bản C tiêu chuẩn khi không có đủ không gian trong bộ đệm (nó trả về -1 trong đó hàm tiêu chuẩn trả về độ dài cần thiết). Không hoàn toàn rõ ràng rằng phiên bản Microsoft null chấm dứt đầu ra của nó trong các điều kiện lỗi, trong khi phiên bản C tiêu chuẩn thì có.

Cũng lưu ý câu trả lời cho Bạn có sử dụng các chức năng an toàn của TR 24731 không? (xem MSDN cho phiên bản Microsoft của vsprintf_s()) và giải pháp Mac để biết các giải pháp thay thế an toàn cho các chức năng thư viện tiêu chuẩn C không an toàn?


ôi, xấu xa, không bao giờ nghĩ đến điều đó. Mặt khác ... :)
Giáo sư Falken

ah, tôi nghĩ MS vsprintf () cắn tôi, và tôi nhặt đó - 1 thói quen
GS Falken

4

Một số phiên bản cũ hơn của SunOS đã làm những điều kỳ lạ với snprintf và có thể không NUL-kết thúc đầu ra và có giá trị trả về không khớp với những gì mọi người đang làm, nhưng bất kỳ thứ gì đã được phát hành trong 10 năm qua đều làm được những gì C99 nói.


Tôi nhận thấy rằng XP đã được phát hành cách đây hơn 10 năm một chút. :-)
Giáo sư Falken

Và năm nay nó đã bị xóa sổ. :)
GS Falken

4

Sự mơ hồ bắt đầu từ chính Tiêu chuẩn C. Cả C99 và C11 đều có mô tả snprintfchức năng giống hệt nhau . Đây là mô tả từ C99:

7.19.6.5 snprintf
Tóm tắt hàm
1 #include <stdio.h> int snprintf(char * restrict s, size_t n, const char * restrict format, ...);
Mô tả
2 snprintfHàm tương đương với fprintf, ngoại trừ việc đầu ra được ghi vào một mảng (được chỉ định bởi đối số s) chứ không phải vào một dòng. Nếu nlà 0, không có gì được viết và scó thể là một con trỏ rỗng. Nếu không, các ký tự đầu ra ngoài n-1st sẽ bị loại bỏ thay vì được ghi vào mảng và ký tự null được viết ở cuối các ký tự thực sự được ghi vào mảng. Nếu việc sao chép diễn ra giữa các đối tượng chồng lên nhau, hành vi đó là không xác định.
Trả về
3 snprintfHàm trả về số ký tự đã được viếtnđủ lớn, không tính ký tự rỗng kết thúc hoặc giá trị âm nếu xảy ra lỗi mã hóa. Do đó, đầu ra kết thúc bằng null đã được ghi hoàn toàn nếu và chỉ khi giá trị trả về là không âm và nhỏ hơn n.

Một mặt , câu

Nếu không, các ký tự đầu ra ngoài n-1st sẽ bị loại bỏ thay vì được ghi vào mảng và ký tự null được viết ở cuối các ký tự thực sự được viết vào mảng

nói rằng
nếu (các strỏ đến một mảng dài 3 ký tự và) nlà 3, thì 2 ký tự sẽ được viết và các ký tự ngoài mảng thứ 2 sẽ bị loại bỏ ; thì ký tự null được viết sau 2 cái đó (và ký tự null sẽ là ký tự thứ 3 được viết) .

Và điều này tôi tin rằng trả lời câu hỏi ban đầu.
TRẢ LỜI:
Nếu việc sao chép diễn ra giữa các đối tượng chồng lên nhau, hành vi đó là không xác định.
Nếu nlà 0 thì không có gì được ghi vào đầu ra
, ngược lại nếu không gặp lỗi mã hóa, đầu ra LUÔN LUÔN kết thúc bằng rỗng ( bất kể đầu ra có phù hợp với mảng đầu ra hay không ; nếu không thì một số ký tự sẽ bị loại bỏ để đầu ra mảng không bao giờ bị tràn),
nếu không (nếu gặp lỗi mã hóa) đầu ra có thể không bị kết thúc bằng null .

Mặt khác
Câu cuối cùng

Do đó, đầu ra kết thúc bằng null đã được ghi hoàn toàn nếu và chỉ khi giá trị trả về là không âm và nhỏ hơn n

đưa ra sự mơ hồ (hoặc tiếng Anh của tôi không đủ tốt). Tôi có thể giải thích câu này theo ít nhất hai cách:
1. Kết quả đầu ra là null-end nếu và chỉ khi giá trị trả về không âm và nhỏ hơnn (có nghĩa là nếu giá trị trả về không nhỏ hơn n, tức là đầu ra (bao gồm kết thúc ký tự null) không phù hợp với mảng, thì đầu ra không phải là ký tự null ).
2. Kết quả hoàn tất (không có ký tự nào bị loại bỏ) nếu và chỉ khi giá trị trả về không âm và nhỏ hơnn .


Tôi tin rằng cách giải thích 1 ở trên mâu thuẫn với CÂU TRẢ LỜI, gây ra hiểu lầm và thảo luận dài dòng. Đó là lý do tại sao câu cuối cùng mô tả snprintfhàm cần thay đổi để loại bỏ bất kỳ sự mơ hồ nào (điều này tạo cơ sở để viết Đề xuất cho Tiêu chuẩn ngôn ngữ C).
Ví dụ về từ ngữ không mơ hồ mà tôi tin rằng có thể được lấy từ http://en.cppreference.com/w/c/io/fprintf (xem 4)), cảm ơn @ "Martin Ba" cho liên kết.

Xem thêm câu hỏi " snprintf: Có bất kỳ Đề xuất / kế hoạch Tiêu chuẩn C nào để thay đổi mô tả của func này không? ".


4
Cách giải thích 1 của bạn có vẻ không hợp lý chút nào đối với tôi. Tôi phân tích cú pháp câu đó là "Đầu ra (ngẫu nhiên, kết thúc bằng null) đã được viết hoàn toàn nếu ..." mà tôi chỉ có thể hiểu là # 2.
zwol

1
Phủ định của câu "đầu ra bị kết thúc bằng null đã được viết hoàn toàn" là "đầu ra bị kết thúc bằng null chưa được viết hoàn toàn". Chỉ có bấy nhiêu thôi. Bản thân câu bị phủ định không ngụ ý rằng bất cứ điều gì đã được viết (điều này bao gồm đầu ra không kết thúc bằng rỗng không hoàn chỉnh, đầu ra không kết thúc bằng không hoàn chỉnh hoặc ý tưởng màu xanh lục không màu). Một số nơi khác trong tiêu chuẩn cho biết chính xác những gì được viết khi đầu ra không đầy đủ và nơi đó nói rằng đầu ra là null kết thúc trừ khi nó trống (n == 0).
n. 'đại từ' m.
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.