Làm thế nào các mảng ký tự nên được sử dụng như là chuỗi?


10

Tôi hiểu rằng các chuỗi trong C chỉ là mảng ký tự. Vì vậy, tôi đã thử đoạn mã sau, nhưng nó cho kết quả lạ, chẳng hạn như đầu ra rác hoặc sự cố chương trình:

#include <stdio.h>

int main (void)
{
  char str [5] = "hello";
  puts(str);
}

Tại sao điều này không làm việc?

Nó biên dịch sạch sẽ với gcc -std=c17 -pedantic-errors -Wall -Wextra.


Lưu ý: Bài đăng này có nghĩa là được sử dụng như một Câu hỏi thường gặp về kinh điển cho các vấn đề xuất phát từ việc không phân bổ chỗ cho bộ kết thúc NUL khi khai báo chuỗi.

Câu trả lời:


12

Chuỗi AC là một mảng ký tự kết thúc bằng dấu kết thúc null .

Tất cả các ký tự có một giá trị bảng biểu tượng. Bộ kết thúc null là giá trị ký hiệu 0(không). Nó được sử dụng để đánh dấu sự kết thúc của một chuỗi. Điều này là cần thiết vì kích thước của chuỗi không được lưu trữ ở bất cứ đâu.

Do đó, mỗi khi bạn phân bổ chỗ cho một chuỗi, bạn phải bao gồm đủ không gian cho ký tự kết thúc null. Ví dụ của bạn không làm điều này, nó chỉ phân bổ chỗ cho 5 ký tự "hello". Mã chính xác phải là:

char str[6] = "hello";

Hoặc tương tự, bạn có thể viết mã tự viết tài liệu cho 5 ký tự cộng với 1 dấu kết thúc null:

char str[5+1] = "hello";

Khi cấp phát bộ nhớ cho chuỗi một cách linh hoạt trong thời gian chạy, bạn cũng cần phân bổ chỗ cho bộ kết thúc null:

char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);

Nếu bạn không nối thêm bộ kết thúc null ở cuối chuỗi, thì các hàm thư viện mong muốn chuỗi không hoạt động chính xác và bạn sẽ gặp các lỗi "hành vi không xác định" như lỗi đầu ra hoặc sự cố chương trình.

Cách phổ biến nhất để viết một ký tự kết thúc null trong C là sử dụng cái gọi là "chuỗi thoát bát phân", trông như thế này : '\0'. Điều này tương đương 100% với văn bản 0, nhưng mã \đóng vai trò là mã tự ghi lại để nói rằng số 0 rõ ràng có nghĩa là một bộ kết thúc null. Mã như if(str[i] == '\0')sẽ kiểm tra nếu ký tự cụ thể là dấu kết thúc null.

Xin lưu ý rằng thuật ngữ null terminator không liên quan gì đến con trỏ null hoặc NULLmacro! Điều này có thể gây nhầm lẫn - tên rất giống nhau nhưng ý nghĩa rất khác nhau. Đây là lý do tại sao bộ kết thúc null đôi khi được gọi là NULvới một L, không bị nhầm lẫn với NULLhoặc con trỏ null. Xem câu trả lời cho câu hỏi SO này để biết thêm chi tiết.

Các "hello"trong mã của bạn được gọi là một chuỗi chữ . Điều này được coi là một chuỗi chỉ đọc. Các ""phương tiện cú pháp mà trình biên dịch sẽ nối một terminator null trong phần cuối của chuỗi chữ tự động. Vì vậy, nếu bạn in ra, sizeof("hello")bạn sẽ nhận được 6, không phải 5, vì bạn có kích thước của mảng bao gồm cả bộ kết thúc null.


Nó biên dịch sạch với gcc

Thật vậy, thậm chí không một lời cảnh báo. Điều này là do một chi tiết / lỗ hổng tinh vi trong ngôn ngữ C cho phép các mảng ký tự được khởi tạo với một chuỗi ký tự chứa chính xác nhiều ký tự như có chỗ trong mảng và sau đó âm thầm loại bỏ bộ kết thúc null (C17 6.7.9 / 15). Ngôn ngữ cố tình hành xử như thế này vì lý do lịch sử, xem chẩn đoán gcc không nhất quán để khởi tạo chuỗi để biết chi tiết. Cũng lưu ý rằng C ++ khác ở đây và không cho phép sử dụng mẹo / lỗ hổng này.


1
Bạn nên đề cập đến char str[] = "hello";trường hợp.
Jabberwocky

@Jabberwocky Đây là một wiki cộng đồng, vui lòng chỉnh sửa và đóng góp.
Lundin

1
... Và cũng có thể là char *str = "hello";... str[0] = foo;vấn đề.
Jabberwocky

Có lẽ mở rộng ý nghĩa của việc sử dụng sizeofnó để sử dụng nó trên một tham số hàm, đặc biệt khi được định nghĩa là một mảng.
Thời tiết

@WeatherVane nên được bao phủ bởi một Câu hỏi thường gặp khác tại đây: stackoverflow.com/questions/492384/
Lundin

4

Từ tiêu chuẩn C (7.1.1 Định nghĩa thuật ngữ)

1 Chuỗi là một chuỗi các ký tự liền kề được kết thúc bởi và bao gồm ký tự null đầu tiên. Thay vào đó, thuật ngữ chuỗi đa bào đôi khi được sử dụng để nhấn mạnh việc xử lý đặc biệt được đưa ra cho các ký tự đa chuỗi có trong chuỗi hoặc để tránh nhầm lẫn với một chuỗi rộng. Một con trỏ tới một chuỗi là một con trỏ tới ký tự ban đầu (địa chỉ thấp nhất) của nó. Độ dài của chuỗi là số byte đứng trước ký tự null và giá trị của chuỗi là chuỗi các giá trị của các ký tự được chứa theo thứ tự.

Trong tuyên bố này

char str [5] = "hello";

chuỗi ký tự "hello"có biểu diễn bên trong như

{ 'h', 'e', 'l', 'l', 'o', '\0' }

do đó, nó có 6 ký tự bao gồm số 0 kết thúc. Các phần tử của nó được sử dụng để khởi tạo mảng ký tự strchỉ dành khoảng 5 ký tự.

Tiêu chuẩn C (đối diện với Tiêu chuẩn C ++) cho phép khởi tạo một mảng ký tự như vậy khi số 0 kết thúc của một chuỗi ký tự không được sử dụng làm bộ khởi tạo.

Tuy nhiên, kết quả là mảng ký tự strkhông chứa chuỗi.

Nếu bạn muốn mảng đó chứa một chuỗi bạn có thể viết

char str [6] = "hello";

hoặc chỉ

char str [] = "hello";

Trong trường hợp cuối cùng, kích thước của mảng ký tự được xác định từ số lượng khởi tạo của chuỗi ký tự bằng 6.


0

Tất cả các chuỗi có thể được coi là một mảng các ký tự ( ), tất cả các mảng ký tự có thể được coi là các chuỗi ( Không ).

Tại sao không? Và tại sao nó lại là vấn đề?

Ngoài các câu trả lời khác giải thích rằng độ dài của chuỗi không được lưu trữ ở bất kỳ đâu như một phần của chuỗi và các tham chiếu đến tiêu chuẩn nơi chuỗi được xác định, mặt trái là "Làm thế nào để các hàm thư viện C xử lý chuỗi?"

Trong khi một mảng ký tự có thể chứa các ký tự giống nhau, nó chỉ đơn giản là một mảng các ký tự trừ khi ký tự cuối cùng được theo sau bởi ký tự kết thúc nul . Đó nul-chấm dứt nhân vật là những gì cho phép các mảng kí tự phải được xem xét (xử lý như) một chuỗi.

Tất cả các hàm trong C mong đợi một chuỗi làm đối số đều mong muốn chuỗi ký tự sẽ bị chấm dứt . Tại sao?

Nó phải làm với cách tất cả các hàm chuỗi hoạt động. Vì độ dài không được bao gồm như là một phần của mảng, các hàm chuỗi, quét về phía trước trong mảng cho đến khi tìm thấy ký tự nul (ví dụ '\0'- tương đương với số thập phân 0). Xem Bảng và Mô tả ASCII . Bất kể cho dù bạn đang sử dụng strcpy, strchr, strcspn, vv .. Tất cả các chức năng chuỗi dựa trên nul-chấm dứt nhân vật có mặt để xác định nơi cuối chuỗi đó là.

Một so sánh của hai chức năng tương tự từ string.hsẽ nhấn mạnh tầm quan trọng của nhân vật kết thúc . Lấy ví dụ:

    char *strcpy(char *dest, const char *src);

Các strcpychức năng đơn giản là bản sao byte từ srcđể destcho đến khi nul-chấm dứt nhân vật được tìm thấy kể strcpynơi để ngăn chặn sao chép ký tự. Bây giờ có chức năng tương tự memcpy:

    void *memcpy(void *dest, const void *src, size_t n);

Hàm thực hiện một hoạt động tương tự, nhưng không xem xét hoặc yêu cầu srctham số là một chuỗi. Vì memcpykhông thể đơn giản quét về phía trước trong srcviệc sao chép byte cho destđến khi đạt được ký tự kết thúc nul , nên nó yêu cầu số byte rõ ràng để sao chép làm tham số thứ ba. Tham số thứ ba này cung cấp memcpythông tin kích thước tương tự strcpycó thể xuất phát đơn giản bằng cách quét về phía trước cho đến khi tìm thấy một ký tự kết thúc nul .

(cũng nhấn mạnh những gì sai trong strcpy(hoặc bất kỳ chức năng nào mong đợi một chuỗi) nếu bạn không cung cấp chức năng với một chuỗi kết thúc không có ý nghĩa - nó không biết dừng lại ở đâu và sẽ vui vẻ chạy đua qua phần còn lại của phân đoạn bộ nhớ của bạn gọi Hành vi không xác định cho đến khi một ký tự nul tình cờ được tìm thấy ở đâu đó trong bộ nhớ - hoặc xảy ra lỗi Phân đoạn)

Đó là lý do tại sao các hàm mong đợi một chuỗi kết thúc nul phải được thông qua một chuỗi kết thúc nultại sao nó quan trọng .


0

Theo trực giác ...

Hãy nghĩ về một mảng như một biến (giữ các thứ) và một chuỗi như một giá trị (có thể được đặt trong một biến).

Họ chắc chắn không phải là điều tương tự. Trong trường hợp của bạn, biến quá nhỏ để giữ chuỗi, vì vậy chuỗi bị cắt. ("chuỗi trích dẫn" trong C có ký tự null ẩn ở cuối.)

Tuy nhiên, có thể lưu trữ một chuỗi trong một mảng lớn hơn nhiều so với chuỗi.

Lưu ý rằng các toán tử gán và so sánh thông thường ( = == <v.v.) không hoạt động như bạn mong đợi. Nhưng strxyzhọ chức năng đến khá gần, một khi bạn biết bạn đang làm gì. Xem C FAQ trên chuỗimảng .

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.