Tại sao bạn nên sử dụng strncpy thay vì strcpy?


83

Chỉnh sửa: Tôi đã thêm nguồn cho ví dụ.

Tôi đã xem qua ví dụ này :

char source[MAX] = "123456789";
char source1[MAX] = "123456789";
char destination[MAX] = "abcdefg";
char destination1[MAX] = "abcdefg";
char *return_string;
int index = 5;

/* This is how strcpy works */
printf("destination is originally = '%s'\n", destination);
return_string = strcpy(destination, source);
printf("after strcpy, dest becomes '%s'\n\n", destination);

/* This is how strncpy works */
printf( "destination1 is originally = '%s'\n", destination1 );
return_string = strncpy( destination1, source1, index );
printf( "After strncpy, destination1 becomes '%s'\n", destination1 );

Cái nào đã tạo ra đầu ra này:

đích ban đầu là = 'abcdefg'
Sau strcpy, đích trở thành '123456789'

đích1 ban đầu là = 'abcdefg'
Sau strncpy, đích1 trở thành '12345fg'

Điều đó khiến tôi tự hỏi tại sao mọi người lại muốn có hiệu ứng này. Có vẻ như nó sẽ là khó hiểu. Chương trình này khiến tôi nghĩ về cơ bản bạn có thể sao chép tên của ai đó (ví dụ: Tom Brokaw) với Tom Bro763.

Lợi thế của việc sử dụng strncpy() hơn là strcpy() gì?


81
Tôi nghĩ bạn muốn hỏi "Tại sao trên trái đất, bất cứ ai sẽ sử dụng strcpythay vì strncpy?"
Sam Harwell,

5
Khi tôi là kỹ sư cho khóa học lập trình học kỳ đầu tiên bằng C, tôi đã đảm bảo với các sinh viên của mình rằng bất kỳ cách sử dụng nào của các phương pháp như vậy getlinesẽ dẫn đến kết quả không chính xác khi tôi chấm điểm chúng dựa trên các đầu vào được xây dựng cẩn thận. :)
Sam Harwell

4
Tôi nghĩ rằng bạn đã hiểu sai những gì mã thực sự làm. Hãy xem kỹ hơn.
Emil H

6
Thực sự là đáng tiếc C không bao giờ có một thư viện tiêu chuẩn tốt cho chuỗi.
starblue

7
Nó không đáng tiếc lắm đâu. Ý tôi là, nó hoàn toàn phá vỡ tôi, và làm cho ngôn ngữ cấp cao hơn thú vị hơn nhiều :)
Carson Myers

Câu trả lời:


98

strncpychống tràn bộ đệm bằng cách yêu cầu bạn đặt một độ dài vào đó. strcpyphụ thuộc vào dấu vết \0, có thể không phải lúc nào cũng xảy ra.

Thứ hai, lý do tại sao bạn chọn chỉ sao chép 5 ký tự trên chuỗi 7 ký tự là vượt quá tôi, nhưng nó tạo ra hành vi mong đợi. Nó chỉ sao chép các nký tự đầu tiên , đâu nlà đối số thứ ba.

Tất ncả các chức năng được sử dụng như mã hóa phòng thủ chống lại sự cố tràn bộ đệm. Vui lòng sử dụng chúng thay cho các hàm cũ hơn, chẳng hạn như strcpy.


47
Xem lysator.liu.se/c/rat/d11.html : strncpyban đầu được đưa vào thư viện C để xử lý các trường tên có độ dài cố định trong các cấu trúc như mục nhập thư mục. Các trường như vậy không được sử dụng giống như các chuỗi: null ở cuối là không cần thiết đối với trường có độ dài tối đa và việc đặt các byte ở cuối cho các tên ngắn hơn thành null đảm bảo so sánh trường khôn ngoan hiệu quả. strncpyVề nguồn gốc không phải là "strcpy bị ràng buộc" và Ủy ban đã ưu tiên công nhận hoạt động hiện có hơn là thay đổi chức năng để phù hợp hơn với việc sử dụng đó.
Sinan Ünür

35
Tôi không chắc tại sao điều này lại nhận được nhiều phiếu bầu - strncpy không bao giờ được coi là một giải pháp thay thế an toàn hơn cho strcpy và thực tế là không an toàn hơn vì nó không loại bỏ chuỗi. Nó cũng có chức năng khác ở chỗ nó nâng cao độ dài được cung cấp bằng các ký tự NUL. Như caf đã nói trong câu trả lời của mình - nó là để ghi đè các chuỗi trong một mảng kích thước cố định.
Que thăm

26
Phần còn lại thực tế rằng strncpykhông một phiên bản an toàn hơn strcpy.
Sinan Ünür

7
@Sinan: Tôi chưa bao giờ nói rằng nó an toàn hơn. Nó phòng thủ. Nó buộc bạn phải đưa ra một chiều dài, giúp bạn suy nghĩ về những gì bạn đang làm. Có nhiều giải pháp tốt hơn, nhưng thực tế vẫn là mọi người sẽ (và làm) sử dụng strncpythay strcpyvì nó là một chức năng phòng thủ hơn nhiều ... đó là những gì tôi đã nói.
Eric

9
Các hàm n đều được sử dụng làm mã phòng thủ chống lại sự cố tràn bộ đệm. Vui lòng sử dụng chúng thay cho các hàm cũ hơn, chẳng hạn như strcpy. Điều này đúng với snprintf, nhưng không liên quan strncatvà hoàn toàn không đúng đối với strncpy. Làm thế nào mà câu trả lời này lại có thể nhận được nhiều ủng hộ đến vậy? Nó cho thấy tình hình tồi tệ như thế nào liên quan đến chức năng không có thật này. Sử dụng nó không phải là phòng thủ: trong hầu hết các tình huống, lập trình viên không hiểu ngữ nghĩa của nó và tạo ra một chuỗi có thể kết thúc khác 0.
chqrlie

178

Các strncpy()chức năng được thiết kế với một vấn đề rất cụ thể trong tâm trí: thao tác chuỗi được lưu trữ theo cách của ban mục thư mục UNIX. Chúng sử dụng một mảng có kích thước cố định và dấu chấm dứt nul chỉ được sử dụng nếu tên tệp ngắn hơn mảng.

Đó là những gì đằng sau hai điều kỳ lạ của strncpy():

  • Nó không đặt một nul-terminator vào đích nếu nó được lấp đầy hoàn toàn; và
  • Nó luôn luôn lấp đầy điểm đến, với nuls nếu cần thiết.

Để "an toàn hơn strcpy()", bạn nên sử dụng strncat()như sau:

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

Điều đó sẽ luôn kết thúc kết quả và sẽ không sao chép nhiều hơn mức cần thiết.


Nhưng, tất nhiên, strncpy không phải lúc nào những gì bạn muốn một trong hai: strncpy chấp nhận số lượng tối đa của nhân vật để bổ sungkhông kích thước bộ đệm địa điểm ... Nhưng đó chỉ là một điều nhỏ, vì vậy có thể sẽ không là một vấn đề, trừ khi bạn đang cố gắng nối một chuỗi với một chuỗi khác.
David Wolever

Tôi không biết lý do của nó, và nó rất liên quan đến những gì tôi đang nghiên cứu atm.
Matt Joiner

Hàm strncpy () được thiết kế để lưu trữ các chuỗi ở định dạng null-padded có độ dài cố định. Định dạng như vậy được sử dụng cho các mục nhập thư mục Unix ban đầu, nhưng cũng được sử dụng ở vô số nơi khác, vì nó cho phép một chuỗi 0-N byte được lưu trữ trong N byte bộ nhớ. Thậm chí ngày nay, nhiều cơ sở dữ liệu sử dụng chuỗi không đệm trong các trường chuỗi có độ dài cố định của chúng. Sự nhầm lẫn với strncpy () bắt nguồn từ thực tế là nó chuyển đổi chuỗi sang định dạng FLNP. Nếu những gì người ta cần là một chuỗi FLNP, điều đó thật tuyệt vời. Nếu một người cần một chuỗi kết thúc bằng null, người đó phải tự cung cấp chuỗi kết thúc.
supercat

1
tại sao chúng ta cần viết dest[0] = '\0';trước khi gọi strncat? Bạn có thể giải thích thưa ông?
snr

4
@snr: strncat()nối chuỗi nguồn vào cuối chuỗi đích. Chúng tôi chỉ muốn sao chép chuỗi nguồn đến đích, vì vậy trước tiên chúng tôi đặt đích thành chuỗi trống - đó là những gì dest[0] = '\0';.
caf

34

Mặc dù tôi biết ý định đằng sau strncpy, nhưng nó không thực sự là một chức năng tốt. Tránh cả hai. Raymond Chen giải thích .

Cá nhân tôi, kết luận của tôi chỉ đơn giản là tránh strncpyvà tất cả những người bạn của nó nếu bạn đang xử lý các chuỗi bị kết thúc bằng null. Mặc dù có "str" ​​trong tên, các hàm này không tạo ra các chuỗi kết thúc bằng null. Chúng chuyển đổi một chuỗi được kết thúc bằng null thành một bộ đệm ký tự thô. Sử dụng chúng trong đó một chuỗi bị kết thúc bằng null được mong đợi như bộ đệm thứ hai là sai rõ ràng. Bạn không chỉ không nhận được kết thúc null thích hợp nếu nguồn quá dài, mà nếu nguồn ngắn, bạn sẽ nhận được phần đệm rỗng không cần thiết.

Xem thêm Tại sao strncpy không an toàn?


27

strncpy KHÔNG an toàn hơn strcpy, nó chỉ giao dịch một loại lỗi với một loại lỗi khác. Trong C, khi xử lý chuỗi C, bạn cần biết kích thước của bộ đệm của mình, không có cách nào xung quanh nó. strncpy đã được biện minh cho thứ thư mục được đề cập bởi những người khác, nhưng nếu không, bạn không bao giờ nên sử dụng nó:

  • nếu bạn biết độ dài của chuỗi và bộ đệm, tại sao lại sử dụng strncpy? Tốt nhất là lãng phí sức mạnh tính toán (thêm số 0 vô ích)
  • nếu bạn không biết độ dài, thì bạn có nguy cơ âm thầm cắt ngắn chuỗi của mình, điều này không tốt hơn nhiều so với lỗi tràn bộ đệm

Tôi nghĩ đây là một mô tả tốt cho strncpy, vì vậy tôi đã bình chọn nó. strncpy có những rắc rối riêng. Tôi đoán đó là lý do mà ví dụ như glib có phần mở rộng của riêng nó. Và có, thật không may khi bạn là lập trình viên phải nhận thức được Kích thước của tất cả các mảng. Các decison có 0 chấm dứt mảng char như chuỗi, có chi phí tất cả chúng ta thắm thiết ....
Friedrich

1
Chuỗi không có đệm là một điều khá phổ biến khi lưu trữ dữ liệu trong các tệp định dạng cố định. Chắc chắn, sự phổ biến của những thứ như công cụ cơ sở dữ liệu và XML, cùng với kỳ vọng ngày càng tăng của người dùng, đã khiến các tệp định dạng cố định ít phổ biến hơn so với 20 năm trước. Tuy nhiên, những tệp như vậy thường là phương tiện lưu trữ dữ liệu hiệu quả nhất về thời gian. Ngoại trừ khi có sự chênh lệch lớn giữa độ dài dự kiến ​​và độ dài tối đa của dữ liệu trong một bản ghi, thì việc đọc một bản ghi dưới dạng một đoạn nhỏ chứa một số dữ liệu không sử dụng sẽ nhanh hơn nhiều so với đọc một bản ghi được chia thành nhiều đoạn.
supercat

Chỉ cần tiếp quản bảo trì mã kế thừa, đã sử dụng g_strlcpy (), do đó không phải chịu sự thiếu hiệu quả của phần đệm, nhưng đủ chắc chắn, số byte được chuyển KHÔNG được duy trì, vì vậy mã đã âm thầm cắt bớt kết quả.
user2548100

21

Những gì bạn đang tìm là hàm strlcpy()luôn kết thúc chuỗi bằng 0 và khởi tạo bộ đệm. Nó cũng có thể phát hiện tràn. Chỉ có vấn đề, nó không (thực sự) di động và chỉ có trên một số hệ thống (BSD, Solaris). Vấn đề với chức năng này là nó mở ra một hộp sâu khác có thể thấy trong các cuộc thảo luận trên http://en.wikipedia.org/wiki/Strlcpy

Ý kiến ​​cá nhân của tôi là nó hữu ích hơn rất nhiều so với strncpy()strcpy(). Nó có hiệu suất tốt hơn và là một người bạn đồng hành tốt snprintf(). Đối với các nền tảng không có nó, nó tương đối dễ thực hiện. (Đối với giai đoạn phát triển của một ứng dụng, tôi thay thế hai chức năng này ( snprintf()strlcpy()) bằng một phiên bản bẫy hủy bỏ chương trình một cách tàn bạo do lỗi tràn hoặc cắt bộ đệm. Điều này cho phép nhanh chóng bắt được những kẻ vi phạm tồi tệ nhất. Đặc biệt nếu bạn làm việc trên cơ sở mã từ người khác .

CHỈNH SỬA: strlcpy()có thể được thực hiện dễ dàng:

size_t strlcpy(char *dst, const char *src, size_t dstsize)
{
  size_t len = strlen(src);
  if(dstsize) {
    size_t bl = (len < dstsize-1 ? len : dstsize-1);
    ((char*)memcpy(dst, src, bl))[bl] = 0;
  }
  return len;
}

3
Bạn có thể viết rằng strlcpy có sẵn trên hầu hết mọi thứ khác ngoài Linux và Windows! Tuy nhiên, nó được cấp phép BSD, vì vậy bạn chỉ có thể thả nó vào một trong các thư viện của mình và sử dụng nó từ đó.
Michael van der Westhuizen

Bạn có thể muốn thêm một bài kiểm tra dstsize > 0và không làm gì nếu không.
chqrlie

Vâng bạn đã đúng. Tôi sẽ thêm séc vì nếu không có nó a dstsizesẽ kích hoạt memcpyđộ dài lentrên bộ đệm đích và làm tràn nó.
Patrick Schlüter

Thêm một để thúc đẩy các giải pháp tốt. Nhiều người cần biết về strlcpy bởi vì mọi người tiếp tục phát minh lại nó một cách kém cỏi.
rsp

@MichaelvanderWesthuizen Nó có sẵn trên Linux, chỉ không có trong glibc. Xem câu trả lời của tôi để biết thêm thông tin (1) (2) (3)
rsp

3

Các strncpy()chức năng là một an toàn hơn: bạn phải vượt qua độ dài tối đa bộ đệm đích có thể chấp nhận. Nếu không, có thể xảy ra trường hợp chuỗi nguồn không được kết thúc chính xác 0, trong trường hợp đó strcpy()hàm có thể ghi nhiều ký tự hơn vào đích, làm hỏng bất kỳ thứ gì có trong bộ nhớ sau bộ đệm đích. Đây là sự cố tràn bộ đệm được sử dụng trong nhiều lần khai thác

Ngoài ra, đối với các hàm API POSIX như read()không đặt số 0 kết thúc trong bộ đệm, nhưng trả về số byte đã đọc, bạn sẽ đặt số 0 theo cách thủ công hoặc sao chép bằng cách sử dụng strncpy().

Trong mã ví dụ của bạn, indexthực sự không phải là một chỉ số, nhưng một count- nó nói có bao nhiêu ký tự tối đa là sao chép từ nguồn tới đích. Nếu không có byte nào trong số n byte đầu tiên của nguồn, chuỗi được đặt ở đích sẽ không bị kết thúc bằng rỗng


1

strncpy điền vào đích bằng '\ 0' cho kích thước của nguồn, mặc dù kích thước của đích nhỏ hơn ....

manpage:

Nếu độ dài của src nhỏ hơn n, hàm strncpy () sẽ đệm phần còn lại của đích bằng các byte rỗng.

và không chỉ phần còn lại ... cũng sau điều này cho đến khi đạt đến n ký tự. Và do đó bạn nhận được tràn ... (xem triển khai trang người đàn ông)


3
strncpy điền điểm đến bằng '\ 0' cho kích thước của nguồn, mặc dù kích thước của điểm đến nhỏ hơn .... Tôi e rằng câu lệnh này là sai và khó hiểu: strncpyđiền điểm đến bằng '\ 0' cho đối số kích thước, nếu độ dài của nguồn nhỏ hơn. Đối số kích thước không phải là kích thước của nguồn, không phải là số ký tự tối đa để sao chép từ nguồn, vì nó là strncatkích thước của đích.
chqrlie

@chqrlie: Chính xác. Một lợi thế của strncpycác hoạt động sao chép khác là nó đảm bảo rằng toàn bộ đích sẽ được ghi. Vì các trình biên dịch có thể cố gắng tạo ra "sự sáng tạo" khi sao chép các cấu trúc có chứa một số Giá trị Không xác định, nên việc đảm bảo rằng mọi mảng ký tự trong cấu trúc được viết đầy đủ có thể là cách đơn giản nhất để ngăn chặn "sự bất ngờ".
supercat

@supercat: một lợi thế rất nhỏ so với trường hợp cụ thể này ... nhưng đích phải được vá sau khi cuộc gọi đến strncpyđể đảm bảo chấm dứt null: strncpy(dest, src, dest_size)[dest_size - 1] = '\0';
chqrlie

@chqrlie: Có yêu cầu byte null ở cuối hay không sẽ phụ thuộc vào dữ liệu được cho là đại diện. Việc sử dụng dữ liệu có phần đệm bằng không thay vì kết thúc bằng 0 trong một cấu trúc không phổ biến như trước đây, nhưng nếu ví dụ: định dạng tệp đối tượng sử dụng tên phần 8 byte, thì có thể có char[8]trong một cấu trúc xử lý mọi thứ đến 8 ký tự có thể đẹp hơn so với việc sử dụng một char[8]nhưng chỉ có thể xử lý 7 ký tự, hoặc phải sao chép một chuỗi vào char[9]bộ đệm và sau đó memcpynó đến đích.
supercat

@chqrlie: Hầu hết các mã thực hiện mọi thứ với chuỗi phải biết chúng có thể tồn tại trong bao lâu và không nên chạy một cách mù quáng với các charcon trỏ cho đến khi chúng chạm mức 0. Điều duy nhất mà các chuỗi có kết thúc bằng 0 thực sự tốt là chuỗi ký tự và thậm chí có một tiền tố được mã hóa có độ dài thay đổi có lẽ sẽ tốt hơn. Đối với hầu hết mọi thứ khác, sẽ tốt hơn nếu có các chuỗi có tiền tố là độ dài hoặc có tiền tố đặc biệt sẽ cho biết rằng char*thực sự là một cái gì đó như thế nào struct stringInfo {char header[4]; char *realData; size_t length; size_t size;}.
supercat

-1

Điều này có thể được sử dụng trong nhiều trường hợp khác, nơi bạn chỉ cần sao chép một phần của chuỗi ban đầu của mình vào đích. Sử dụng strncpy (), bạn có thể sao chép một phần giới hạn của chuỗi gốc so với strcpy (). Tôi thấy mã bạn đưa lên đến từ publib.boulder.ibm.com .


-1

Điều đó phụ thuộc vào yêu cầu của chúng tôi. Đối với người dùng windows

Chúng ta sử dụng strncpy bất cứ khi nào chúng ta không muốn sao chép toàn bộ chuỗi hoặc chúng ta chỉ muốn sao chép n số ký tự. Nhưng strcpy sao chép toàn bộ chuỗi bao gồm kết thúc ký tự null.

Các liên kết này sẽ giúp bạn biết thêm về strcpy và strncpy và chúng ta có thể sử dụng ở đâu.

về strcpy

về strncpy


-8

strncpy là một phiên bản an toàn hơn của strcpy vì thực tế là bạn không bao giờ nên sử dụng strcpy vì lỗ hổng tràn bộ đệm tiềm ẩn của nó khiến hệ thống của bạn dễ bị tấn công bởi tất cả các loại


6
Xem lysator.liu.se/c/rat/d11.html : Hàm strncpy strncpy ban đầu được đưa vào thư viện C để xử lý các trường tên có độ dài cố định trong các cấu trúc như mục nhập thư mục. Các trường như vậy không được sử dụng giống như các chuỗi: null ở cuối là không cần thiết đối với trường có độ dài tối đa và việc đặt các byte ở cuối cho các tên ngắn hơn thành null đảm bảo so sánh trường khôn ngoan hiệu quả. strncpy về nguồn gốc không phải là một `` strcpy bị ràng buộc '' và Ủy ban đã ưu tiên công nhận hoạt động hiện có hơn là thay đổi chức năng để phù hợp hơn với việc sử dụng đó.
Sinan Ünür
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.