Là khởi tạo một char [] với một chuỗi thực hành xấu theo nghĩa đen?


44

Tôi đã đọc một chủ đề có tiêu đề "strlen vs sizeof" trên CodeGurumột trong những câu trả lời nói rằng "đó là cách thực hành tồi [sic] để khởi tạo [sic] một charmảng với một chuỗi ký tự."

Điều này có đúng không, hay đó chỉ là ý kiến ​​của anh ấy (mặc dù là "thành viên ưu tú")?


Đây là câu hỏi ban đầu:

#include <stdio.h>
#include<string.h>
main()
{
    char string[] = "october";
    strcpy(string, "september");

    printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
    return 0;
}

đúng. kích thước nên là chiều dài cộng 1 có?

đây là đầu ra

the size of september is 8 and the length is 9

kích thước nên là 10 chắc chắn. Nó giống như tính toán chuỗi sizeof trước khi nó được thay đổi bởi strcpy nhưng độ dài sau.

Có cái gì đó sai với cú pháp của tôi hoặc những gì?


Đây là câu trả lời :

Dù sao, đó là cách thực hành tồi để khởi tạo một mảng char với một chuỗi ký tự. Vì vậy, luôn luôn làm một trong những điều sau đây:

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");

Lưu ý "const" trên dòng đầu tiên. Có thể là tác giả giả định c ++ thay vì c? Trong c ++, đó là "thực hành xấu", bởi vì một chữ nên là const và bất kỳ trình biên dịch c ++ nào gần đây sẽ đưa ra cảnh báo (hoặc lỗi) về việc gán một const constal cho một mảng không const.
André

@ André C ++ định nghĩa chuỗi ký tự là mảng const, bởi vì đó là cách an toàn duy nhất để xử lý chúng. Rằng C không phải là vấn đề, vì vậy bạn có một quy tắc xã hội thực thi điều an toàn
Caleth

@Caleth. Tôi biết, tôi đã cố gắng nhiều hơn để tranh luận rằng tác giả của câu trả lời đang tiếp cận "thực tiễn xấu" từ góc độ c ++.
André

@ André, đó không phải là một thực tiễn tồi trong C ++, vì đó không phải là một thực tiễn , đó là lỗi loại thẳng. Đó phải là một lỗi loại trong C, nhưng không phải vậy, vì vậy bạn phải có quy tắc hướng dẫn kiểu cho bạn biết "Nó bị cấm"
Caleth

Câu trả lời:


59

Dù sao, đó là cách thực hành tồi để khởi tạo một mảng char với một chuỗi ký tự.

Tác giả của bình luận đó không bao giờ thực sự biện minh cho nó, và tôi thấy tuyên bố khó hiểu.

Trong C (và bạn đã gắn thẻ này là C), đó là cách duy nhất để khởi tạo một mảng charcó giá trị chuỗi (khởi tạo khác với gán). Bạn có thể viết

char string[] = "october";

hoặc là

char string[8] = "october";

hoặc là

char string[MAX_MONTH_LENGTH] = "october";

Trong trường hợp đầu tiên, kích thước của mảng được lấy từ kích thước của trình khởi tạo. Các chuỗi ký tự được lưu trữ dưới dạng các mảng charcó 0 byte kết thúc, vì vậy kích thước của mảng là 8 ('o', 'c', 't', 'o', 'b', 'e', ​​'r', 0). Trong hai trường hợp thứ hai, kích thước của mảng được chỉ định là một phần của khai báo (8 và MAX_MONTH_LENGTH, bất cứ điều gì xảy ra).

Những gì bạn không thể làm là viết một cái gì đó như

char string[];
string = "october";

hoặc là

char string[8];
string = "october";

vv Trong trường hợp đầu tiên, tuyên bố stringkhông đầy đủ vì không có kích thước mảng đã được xác định và không có khởi tạo để có những kích thước từ. Trong cả hai trường hợp, =sẽ không hoạt động vì a) một biểu thức mảng như stringcó thể không phải là mục tiêu của một bài tập và b) =toán tử không được xác định để sao chép nội dung của một mảng sang một mảng khác.

Tương tự như vậy, bạn không thể viết

char string[] = foo;

nơi foolà một mảng của char. Hình thức khởi tạo này sẽ chỉ hoạt động với chuỗi ký tự.

BIÊN TẬP

Tôi nên sửa đổi điều này để nói rằng bạn cũng có thể khởi tạo các mảng để giữ một chuỗi với trình khởi tạo kiểu mảng, như

char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};

hoặc là

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII

nhưng nó dễ dàng hơn trong mắt để sử dụng chuỗi ký tự.

CHỈNH SỬA 2

Để gán nội dung của một mảng bên ngoài khai báo, bạn cần sử dụng strcpy/strncpy(đối với các chuỗi kết thúc 0) hoặc memcpy(đối với bất kỳ loại mảng nào khác):

if (sizeof string > strlen("october"))
  strcpy(string, "october");

hoặc là

strncpy(string, "october", sizeof string); // only copies as many characters as will
                                           // fit in the target buffer; 0 terminator
                                           // may not be copied, but the buffer is
                                           // uselessly completely zeroed if the
                                           // string is shorter!


@KeithThndry: không đồng ý, chỉ cần thêm nó cho đầy đủ.
John Bode

16
Xin lưu ý rằng đó char[8] str = "october";là thực hành xấu. Tôi đã phải theo nghĩa đen char tự tin vào mình để chắc chắn rằng nó không phải là một tràn và nó phá vỡ dưới bảo trì ... ví dụ như sửa chữa một lỗi chính tả từ seprateđến separatesẽ phá vỡ nếu kích thước không được cập nhật.
djechlin

1
Tôi đồng ý với djechlin, đó là thực tế xấu vì những lý do được đưa ra. Câu trả lời của JohnBode hoàn toàn không bình luận về khía cạnh "thực hành xấu" (đây là phần chính của câu hỏi !!), nó chỉ giải thích những gì bạn có thể hoặc không thể làm để khởi tạo mảng.
mastov

Giá trị nhỏ: Giá trị 'độ dài "được trả về strlen()không bao gồm ký tự null, sử dụng MAX_MONTH_LENGTHđể giữ kích thước tối đa cần thiết cho char string[]thường trông có vẻ sai. IMO, MAX_MONTH_SIZEsẽ tốt hơn ở đây.
chux - Phục hồi lại

10

Vấn đề duy nhất tôi nhớ là gán chuỗi ký tự cho char *:

char var1[] = "september";
var1[0] = 'S'; // Ok - 10 element char array allocated on stack
char const *var2 = "september";
var2[0] = 'S'; // Compile time error - pointer to constant string
char *var3 = "september";
var3[0] = 'S'; // Modifying some memory - which may result in modifying... something or crash

Ví dụ: lấy chương trình này:

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

Điều này trên nền tảng của tôi (Linux) gặp sự cố khi nó cố ghi vào trang được đánh dấu là chỉ đọc. Trên các nền tảng khác, nó có thể in 'Tháng Chín', v.v.

Điều đó nói rằng - khởi tạo theo nghĩa đen làm cho số lượng đặt phòng cụ thể để việc này sẽ không hoạt động:

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

Nhưng điều này sẽ

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

Như nhận xét cuối cùng - tôi hoàn toàn không sử dụng strcpy:

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

Trong khi một số trình biên dịch có thể thay đổi nó thành cuộc gọi an toàn strncpythì an toàn hơn nhiều:

char buf[1024];
strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers.
buf[sizeof(buf) - 1] = '\0';

Vẫn có nguy cơ tràn bộ đệm strncpyvì điều đó không chấm dứt chuỗi được sao chép khi độ dài something_elselớn hơn sizeof(buf). Tôi thường đặt char cuối cùng buf[sizeof(buf)-1] = 0để bảo vệ khỏi điều đó, hoặc nếu bufkhông được khởi tạo, sử dụng sizeof(buf) - 1làm độ dài sao chép.
syockit

Sử dụng strlcpyhoặc strcpy_sthậm chí snprintfnếu bạn phải.
dùng253751

Đã sửa. Thật không may, không có cách dễ dàng nào để thực hiện việc này trừ khi bạn có thể làm việc với các trình biên dịch mới nhất ( strlcpysnprintfkhông thể truy cập trực tiếp trên MSVC, ít nhất là các đơn đặt hàng và strcpy_skhông phải trên * nix).
Maciej Piechotka

@MaciejPiechotka: Chà, cảm ơn chúa Unix đã từ chối phụ lục microsoft tài trợ k.
Ded repeatator

6

Một điều mà không chủ đề nào đưa ra là:

char whopping_great[8192] = "foo";

so với

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

Các cựu sẽ làm một cái gì đó như:

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

Cái sau chỉ làm memcpy. Tiêu chuẩn C khẳng định rằng nếu bất kỳ phần nào của mảng được khởi tạo thì tất cả đều như vậy. Vì vậy, trong trường hợp này, tốt hơn là tự làm điều đó. Tôi nghĩ rằng đó có thể là những gì treuss đã nhận được.

Chắc chắn

char whopping_big[8192];
whopping_big[0] = 0;

tốt hơn một trong hai:

char whopping_big[8192] = {0};

hoặc là

char whopping_big[8192] = "";

ps Đối với điểm thưởng, bạn có thể làm:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

để ném một khoảng thời gian biên dịch cho sai số 0 nếu bạn sắp tràn mảng.


5

Chủ yếu bởi vì bạn sẽ không có kích thước của char[]một biến / cấu trúc mà bạn có thể dễ dàng sử dụng trong chương trình.

Mẫu mã từ liên kết:

 char string[] = "october";
 strcpy(string, "september");

stringđược phân bổ trên ngăn xếp dài 7 hoặc 8 ký tự. Tôi không thể nhớ lại liệu nó có bị chấm dứt theo cách này hay không - chủ đề bạn liên kết để nói rằng nó là.

Sao chép "tháng chín" qua chuỗi đó là một tràn bộ nhớ rõ ràng.

Một thách thức khác xảy ra nếu bạn chuyển stringsang hàm khác để hàm kia có thể ghi vào mảng. Bạn cần nói cho hàm khác biết thời gian của mảng là bao lâu để không tạo ra lỗi tràn. Bạn có thể vượt qua stringcùng với kết quả của strlen()nhưng luồng giải thích làm thế nào điều này có thể nổ tung nếu stringkhông bị chấm dứt null.

Tốt hơn hết là bạn nên phân bổ một chuỗi có kích thước cố định (tốt nhất là được xác định là hằng số) và sau đó chuyển mảng và kích thước cố định sang hàm khác. Nhận xét của @John Bode là chính xác và có nhiều cách để giảm thiểu những rủi ro này. Họ cũng đòi hỏi nhiều nỗ lực hơn từ phía bạn để sử dụng chúng.

Theo kinh nghiệm của tôi, giá trị tôi khởi tạo char[]thường là quá nhỏ so với các giá trị khác tôi cần đặt ở đó. Sử dụng một hằng số xác định giúp tránh vấn đề đó.


sizeof stringsẽ cung cấp cho bạn kích thước của bộ đệm (8 byte); sử dụng kết quả của biểu thức đó thay vì strlenkhi bạn quan tâm đến bộ nhớ.
Tương tự, bạn có thể thực hiện kiểm tra trước lệnh gọi strcpyđể xem liệu bộ đệm đích của bạn có đủ lớn cho chuỗi nguồn không : if (sizeof target > strlen(src)) { strcpy (target, src); }.
Có, nếu bạn phải truyền mảng cho hàm, bạn cũng cần chuyển cả kích thước vật lý của nó : foo (array, sizeof array / sizeof *array);. - John Bode


2
sizeof stringsẽ cung cấp cho bạn kích thước của bộ đệm (8 byte); sử dụng kết quả của biểu thức đó thay vì strlenkhi bạn quan tâm đến bộ nhớ. Tương tự, bạn có thể thực hiện kiểm tra trước lệnh gọi strcpyđể xem liệu bộ đệm đích của bạn có đủ lớn cho chuỗi nguồn không : if (sizeof target > strlen(src)) { strcpy (target, src); }. Có, nếu bạn phải truyền mảng cho hàm, bạn cũng cần chuyển cả kích thước vật lý của nó : foo (array, sizeof array / sizeof *array);.
John Bode

1
@JohnBode - cảm ơn, và đó là những điểm tốt. Tôi đã kết hợp nhận xét của bạn vào câu trả lời của tôi.

1
Chính xác hơn, hầu hết các tham chiếu đến tên mảng stringdẫn đến một chuyển đổi ngầm định char*, chỉ đến phần tử đầu tiên của mảng. Điều này làm mất thông tin giới hạn mảng. Một cuộc gọi chức năng chỉ là một trong nhiều bối cảnh trong đó điều này xảy ra. char *ptr = string;là một cái khác. Thậm chí string[0]là một ví dụ về điều này; các []nhà điều hành hoạt động trên con trỏ, không trực tiếp trên mảng. Đọc gợi ý: Phần 6 của FAQ comp.lang.c .
Keith Thompson

Cuối cùng một câu trả lời thực sự đề cập đến câu hỏi!
mastov

2

Tôi nghĩ ý tưởng "thực hành xấu" xuất phát từ thực tế là hình thức này:

char string[] = "october is a nice month";

làm cho ngầm một strcpy từ mã máy nguồn đến ngăn xếp.

Sẽ hiệu quả hơn khi chỉ xử lý một liên kết đến chuỗi đó. Thích với:

char *string = "october is a nice month";

hoặc trực tiếp:

strcpy(output, "october is a nice month");

(nhưng tất nhiên trong hầu hết các mã có lẽ không thành vấn đề)


Nó sẽ không tạo ra một bản sao nếu bạn cố gắng sửa đổi nó? Tôi nghĩ trình biên dịch sẽ thông minh hơn thế
Cole Johnson

1
Còn những trường hợp như char time_buf[] = "00:00";bạn sẽ sửa đổi bộ đệm thì sao? Một char *ký tự được khởi tạo thành một chuỗi ký tự được đặt thành địa chỉ của byte đầu tiên, vì vậy cố gắng sửa đổi nó dẫn đến hành vi không xác định vì phương thức lưu trữ của chuỗi ký tự không xác định (triển khai được xác định), trong khi sửa đổi các byte của a char[]là hoàn toàn hợp pháp vì việc khởi tạo sao chép các byte vào một không gian có thể ghi được phân bổ trên ngăn xếp. Để nói rằng đó là "kém hiệu quả" hoặc "thực hành xấu" mà không xây dựng các sắc thái của char* vs char[]là sai lệch.
Braden hay nhất

-3

Không bao giờ là thời gian thực sự dài, nhưng bạn nên tránh khởi tạo char [] thành chuỗi, bởi vì, "chuỗi" là const char * và bạn đang gán nó cho char *. Vì vậy, nếu bạn chuyển char [] này cho phương thức thay đổi dữ liệu, bạn có thể có hành vi thú vị.

Như commend đã nói tôi trộn một chút char [] với char *, điều đó không tốt vì chúng khác nhau một chút.

Không có gì sai khi gán dữ liệu cho mảng char, nhưng vì ý định sử dụng mảng này là sử dụng nó dưới dạng 'chuỗi' (char *), rất dễ quên rằng bạn không nên sửa đổi mảng này.


3
Sai. Việc khởi tạo sao chép nội dung của chuỗi ký tự thành mảng. Đối tượng mảng không consttrừ khi bạn định nghĩa nó theo cách đó. (Và chuỗi ký tự trong C khôngconst , mặc dù mọi nỗ lực sửa đổi chuỗi ký tự chuỗi đều có hành vi không xác định.) char *s = "literal";Có loại hành vi mà bạn đang nói đến; tốt hơn nên viết làconst char *s = "literal";
Keith Thompson

thực sự là lỗi của tôi, tôi đã trộn char [] với char *. Nhưng tôi sẽ không chắc chắn về việc sao chép nội dung vào mảng. Kiểm tra nhanh với trình biên dịch MS C cho thấy 'char c [] = "asdf";' sẽ tạo 'chuỗi' trong phân đoạn const và sau đó gán địa chỉ này cho biến mảng. Đó thực sự là một lý do tại sao tôi nói về việc tránh các bài tập cho mảng không const.
Dainius

Tôi hoài nghi. Hãy thử chương trình này và cho tôi biết những gì bạn nhận được đầu ra.
Keith Thompson

2
"Và nói chung" asdf "là một hằng số, vì vậy nó nên được khai báo là const."- Lý do tương tự sẽ gọi cho một constngày int n = 42;, vì 42là một hằng số.
Keith Thompson

1
Không quan trọng bạn đang dùng máy gì. Các tiêu chuẩn ngôn ngữ đảm bảo ccó thể sửa đổi. Đó chính xác là một sự đảm bảo mạnh mẽ như một sự 1 + 1đánh giá 2. Nếu chương trình tôi liên kết đến ở trên không làm bất cứ điều gì ngoài việc in EFGH, nó chỉ ra việc thực hiện C không tuân thủ.
Keith Thompson
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.