Các chuỗi C luôn luôn bị chấm dứt, hay nó phụ thuộc vào nền tảng?


13

Ngay bây giờ tôi đang làm việc với các hệ thống nhúng và tìm ra cách để thực hiện các chuỗi trên bộ vi xử lý không có hệ điều hành. Cho đến nay những gì tôi đang làm chỉ là sử dụng ý tưởng về việc NULL chấm dứt các con trỏ ký tự và coi chúng là các chuỗi trong đó NULL biểu thị sự kết thúc. Tôi biết rằng điều này là khá phổ biến, nhưng bạn có thể luôn luôn nghĩ rằng đây là trường hợp?

Lý do tôi hỏi là tôi đã suy nghĩ về việc có thể sử dụng một hệ điều hành thời gian thực vào một lúc nào đó và tôi muốn sử dụng lại càng nhiều mã hiện tại của mình càng tốt. Vì vậy, đối với các lựa chọn khác nhau ngoài kia, tôi có thể mong đợi các chuỗi hoạt động giống nhau không?

Hãy để tôi được cụ thể hơn mặc dù cho trường hợp của tôi. Tôi đang thực hiện một hệ thống nhận và xử lý các lệnh qua một cổng nối tiếp. Tôi có thể giữ mã xử lý lệnh của mình giống nhau không, và sau đó mong đợi rằng các đối tượng chuỗi được tạo trên RTOS (chứa các lệnh) sẽ bị chấm dứt NULL? Hoặc, nó sẽ khác nhau dựa trên hệ điều hành?

Cập nhật

Sau khi được khuyên nên xem câu hỏi này, tôi đã xác định rằng nó không trả lời chính xác những gì tôi đang hỏi. Câu hỏi đặt ra là liệu độ dài của một chuỗi có phải luôn luôn vượt qua hoàn toàn khác với những gì tôi đang hỏi hay không, và mặc dù một số câu trả lời có thông tin hữu ích trong đó, chúng không chính xác là những gì tôi đang tìm kiếm. Các câu trả lời dường như đưa ra lý do tại sao hoặc tại sao không chấm dứt một chuỗi có ký tự null. Sự khác biệt với những gì tôi đang hỏi là liệu tôi có thể ít nhiều mong đợi các chuỗi sinh ra của các nền tảng khác nhau chấm dứt các chuỗi của riêng chúng bằng null, mà không cần phải ra ngoài và thử mọi nền tảng ngoài đó nếu điều đó hợp lý.


3
Tôi đã không sử dụng C trong một thời gian dài, nhưng tôi không thể nghĩ đến một thời gian khi tôi thực hiện một triển khai không sử dụng các chuỗi kết thúc NULL. Đó là một phần của tiêu chuẩn C, nếu tôi nhớ chính xác (như tôi đã nói, đã lâu rồi ...)
MetalMikester

1
Tôi không phải là chuyên gia về C, nhưng theo tôi biết tất cả các chuỗi trong C là mảng char, kết thúc null. Bạn có thể tạo loại chuỗi của riêng mình, nhưng bạn phải tự thực hiện tất cả các hàm thao tác chuỗi.
Machado


1
@MetalMikester Bạn nghĩ rằng thông tin này có thể được tìm thấy trong thông số C tiêu chuẩn?
Snoop

3
@Snoopy Rất có thể, vâng. Nhưng thực sự, khi nói về các chuỗi trong C, chúng chỉ là một chuỗi các ký tự kết thúc bằng NULL và đó là, trừ khi bạn sử dụng một loại thư viện chuỗi không chuẩn nhưng dù sao đó không phải là những gì chúng ta đang nói ở đây. Tôi nghi ngờ bạn sẽ tìm thấy một nền tảng không tôn trọng điều đó, đặc biệt với một trong những điểm mạnh của C là tính di động.
MetalMikester

Câu trả lời:


42

Những thứ được gọi là "chuỗi C" sẽ bị hủy kết thúc trên mọi nền tảng. Đó là cách các hàm thư viện C tiêu chuẩn xác định kết thúc chuỗi.

Trong ngôn ngữ C, không có gì ngăn bạn có một loạt các ký tự không kết thúc bằng null. Tuy nhiên, bạn sẽ phải sử dụng một số phương pháp khác để tránh chạy hết chuỗi.


4
chỉ để thêm vào; thông thường bạn có một số nguyên ở đâu đó để theo dõi độ dài chuỗi và sau đó bạn kết thúc với cấu trúc dữ liệu tùy chỉnh để thực hiện đúng, giống như lớp QString trong Qt
Rudolf Olah

8
Case in point: Tôi làm việc với chương trình C sử dụng ít nhất năm định dạng chuỗi khác nhau: charmảng kết thúc null , charmảng có độ dài được mã hóa trong byte đầu tiên (thường được gọi là "chuỗi Pascal"), wchar_tphiên bản dựa trên cả hai ở trên và charcác mảng kết hợp cả hai phương thức: độ dài được mã hóa trong byte đầu tiên và một ký tự null kết thúc chuỗi.
Đánh dấu

4
@Mark Giao diện với nhiều thành phần / ứng dụng của bên thứ 3 hoặc một mớ hỗn độn mã kế thừa?
Dan đang loay hoay bởi Firelight

2
@DanNeely, tất cả những điều trên. Chuỗi Pascal để giao tiếp với MacOS cổ điển, chuỗi C để sử dụng nội bộ và Windows, chuỗi rộng để thêm hỗ trợ Unicode và chuỗi khốn vì ai đó đã cố gắng khéo léo và tạo một chuỗi có thể giao tiếp với cả MacOS và Windows cùng một lúc.
Đánh dấu

1
@Mark ... và tất nhiên không ai sẵn sàng chi tiền để trả nợ kỹ thuật vì MacOS cổ điển đã chết từ lâu và chuỗi khốn là một cụm sao đôi mỗi khi chúng cần được chạm vào. Sự đồng cảm của tôi.
Dan đang loay hoay bởi Firelight

22

Việc xác định ký tự kết thúc tùy thuộc vào trình biên dịch cho chữ và việc thực hiện thư viện chuẩn cho các chuỗi nói chung. Nó không được xác định bởi hệ điều hành.

Quy ước NULchấm dứt trở lại tiêu chuẩn C và trong hơn 30 năm qua, tôi không thể nói rằng tôi đã chạy vào một môi trường làm bất cứ điều gì khác. Hành vi này đã được mã hóa trong C89 và tiếp tục là một phần của tiêu chuẩn ngôn ngữ C (liên kết là bản nháp của C99):

  • Mục 6.4.5 thiết lập giai đoạn cho các NULchuỗi bị hủy bằng cách yêu cầu một NULchuỗi được thêm vào chuỗi ký tự.
  • Mục 7.1.1 đưa điều đó đến các hàm trong thư viện chuẩn bằng cách định nghĩa một chuỗi là "một chuỗi các ký tự liền kề được chấm dứt bởi và bao gồm ký tự null đầu tiên."

Không có lý do tại sao một người nào đó không thể viết các hàm xử lý các chuỗi bị chấm dứt bởi một số ký tự khác, nhưng cũng không có lý do gì để bỏ tiêu chuẩn đã thiết lập trong hầu hết các trường hợp trừ khi mục tiêu của bạn là phù hợp với các lập trình viên. :-)


2
Một lý do là để tránh phải tìm đi hết chuỗi lặp đi lặp lại.
Paŭlo Ebermann

@ PaŭloEbermann Phải. Với chi phí phải vượt qua hai giá trị thay vì một. Đó là một chút khó chịu nếu bạn chỉ cần vượt qua một chuỗi bằng chữ như trong printf("string: \"%s\"\n", "my cool string"). Cách duy nhất để vượt qua bốn tham số trong trường hợp này (không phải là một loại byte kết thúc nào đó) là xác định một chuỗi giống như std::stringtrong C ++, có vấn đề và hạn chế riêng.
cmaster - phục hồi monica

1
Mục 6.4.5 không yêu cầu một chuỗi ký tự được kết thúc bằng ký tự null. Nó ghi chú rõ ràng " Một chuỗi ký tự theo nghĩa đen không cần phải là một chuỗi (xem 7.1.1), bởi vì một ký tự null có thể được nhúng trong chuỗi đó bằng một chuỗi thoát \ 0. "
bzeaman

1
@bzeaman Chú thích nói rằng bạn có thể xây dựng một chuỗi ký tự không đáp ứng định nghĩa của chuỗi 7.1.1, nhưng câu liên quan đến nó nói rằng các trình biên dịch tuân thủ - NULhủy bỏ chúng bất kể là gì: của giá trị 0 được thêm vào từng chuỗi ký tự đa dòng kết quả từ một chuỗi ký tự hoặc bằng chữ. " Các chức năng thư viện sử dụng định nghĩa của 7.1.1 dừng lại ở lần đầu tiên NULhọ tìm thấy và sẽ không biết hoặc quan tâm rằng các ký tự bổ sung tồn tại ngoài nó.
Blrfl

Tôi đứng sửa. Tôi đã tìm kiếm các thuật ngữ khác nhau như 'null' nhưng đã bỏ lỡ 6.4.5.5 khi đề cập đến 'giá trị 0'.
bzeaman

3

Tôi đang làm việc với các hệ thống nhúng ... không có hệ điều hành ... Tôi ... đang sử dụng ý tưởng về việc NULL chấm dứt các con trỏ ký tự và coi chúng là các chuỗi trong đó NULL biểu thị sự kết thúc. Tôi biết rằng điều này là khá phổ biến, nhưng bạn có thể luôn luôn nghĩ rằng đây là trường hợp?

Không có kiểu dữ liệu chuỗi trong ngôn ngữ C, nhưng có chuỗi ký tự .

Nếu bạn đặt một chuỗi ký tự trong chương trình của mình, nó thường sẽ bị chấm dứt NUL (nhưng xem trường hợp đặc biệt, được thảo luận trong các bình luận bên dưới.) Điều đó có nghĩa là, nếu bạn đặt "foobar"ở một nơi mà const char *giá trị được mong đợi, trình biên dịch sẽ phát ra foobar⊘đến đoạn const / đoạn mã / phần của chương trình của bạn và giá trị của biểu thức sẽ là một con trỏ tới địa chỉ nơi nó lưu fký tự. (Lưu ý: Tôi đang sử dụng để biểu thị byte NUL.)

Ý nghĩa khác trong đó ngôn ngữ C có các chuỗi là, nó có một số thói quen thư viện tiêu chuẩn hoạt động trên các chuỗi ký tự kết thúc NUL. Những thói quen thư viện đó sẽ không tồn tại trong một môi trường kim loại trần trừ khi bạn tự mình chuyển chúng.

Chúng chỉ là mã --- không khác với mã mà bạn tự viết. Nếu bạn không phá vỡ chúng khi bạn chuyển chúng, thì chúng sẽ làm những gì chúng luôn làm (ví dụ: dừng trên NUL.)


2
Re: "Nếu bạn đặt một chuỗi ký tự trong chương trình của mình, nó sẽ luôn bị chấm dứt NUL": Bạn có chắc chắn về điều đó không? Tôi khá chắc chắn rằng (ví dụ) char foo[4] = "abcd";là một cách hợp lệ để tạo ra một mảng gồm bốn ký tự không kết thúc.
ruakh

2
@ruakh, Rất tiếc! đó là một trường hợp mà tôi đã không xem xét. Tôi đã suy nghĩ về một chuỗi ký tự xuất hiện ở một nơi mà một char const * biểu thức được mong đợi. Tôi quên rằng các trình khởi tạo C đôi khi có thể tuân theo các quy tắc khác nhau.
Solomon chậm

@ruakh Chuỗi ký tự được kết thúc bằng NUL. Các mảng thì không.
jamesdlin

2
@ruakh bạn có a char[4]. Đó không phải là một chuỗi, nhưng nó đã được khởi tạo từ một
Caleth 22/03/17

2
@Caleth, "khởi tạo từ một" không phải là điều phải xảy ra trong thời gian chạy. Nếu chúng ta thêm từ khóa staticvào ví dụ của Ruakh, thì trình biên dịch có thể phát ra một "abcd" không kết thúc NUL cho một phân đoạn dữ liệu được khởi tạo để biến được khởi tạo bởi trình tải chương trình. Vì vậy, Ruakh đã đúng: Có ít nhất một trường hợp trong đó sự xuất hiện của một chuỗi ký tự trong một chương trình không yêu cầu trình biên dịch phát ra một chuỗi kết thúc NUL. (ps, tôi thực sự đã biên dịch ví dụ với gcc 5.4.0 và trình biên dịch không phát ra NUL.)
Solomon Slow

2

Như những người khác đã đề cập, việc kết thúc chuỗi null là một quy ước của Thư viện chuẩn C. Bạn có thể xử lý các chuỗi theo bất kỳ cách nào bạn muốn nếu bạn không sử dụng thư viện chuẩn.

Điều này đúng với bất kỳ hệ điều hành nào có trình biên dịch 'C' và đồng thời, bạn có thể viết các chương trình 'C' không chạy dưới hệ điều hành thực như bạn đề cập trong câu hỏi của mình. Một ví dụ sẽ là bộ điều khiển cho một máy in phun mực mà tôi đã thiết kế một lần. Trong các hệ thống nhúng, chi phí bộ nhớ của một hệ điều hành có thể không cần thiết.

Trong các tình huống hạn chế bộ nhớ, tôi sẽ xem xét các đặc điểm của trình biên dịch của tôi, ví dụ như tập lệnh của bộ xử lý. Trong một ứng dụng nơi các chuỗi được xử lý nhiều, có thể nên sử dụng các mô tả như độ dài chuỗi. Tôi đang nghĩ đến một trường hợp CPU đặc biệt hiệu quả khi làm việc với các độ lệch ngắn và / hoặc độ lệch tương đối với các thanh ghi địa chỉ.

Vì vậy, cái nào quan trọng hơn trong ứng dụng của bạn: kích thước và hiệu quả mã, hoặc khả năng tương thích với HĐH hoặc Thư viện? Một sự xem xét khác có thể là khả năng duy trì. Bạn càng đi lạc khỏi hội nghị, người khác sẽ càng khó duy trì.


1

Những người khác đã giải quyết vấn đề rằng trong C, chuỗi phần lớn là những gì bạn tạo ra từ chúng. Nhưng dường như có một số nhầm lẫn trong câu hỏi của bạn, chính bản thân kẻ hủy diệt, và từ một góc độ, đây có thể là điều mà ai đó ở vị trí của bạn lo lắng.

Chuỗi C được kết thúc null. Đó là, họ bị chấm dứt bởi ký tự null NUL. Chúng không bị chấm dứt bởi con trỏ null NULL, đây là một loại giá trị hoàn toàn khác với mục đích hoàn toàn khác.

NULđược đảm bảo có giá trị nguyên bằng không. Trong chuỗi, nó cũng sẽ có kích thước của kiểu ký tự cơ bản, thường sẽ là 1.

NULLkhông được đảm bảo có một loại số nguyên nào cả. NULLđược thiết kế để sử dụng trong ngữ cảnh con trỏ và thường được dự kiến ​​sẽ có một loại con trỏ, không nên chuyển đổi thành ký tự hoặc số nguyên nếu trình biên dịch của bạn tốt. Mặc dù định nghĩa NULLliên quan đến glyph 0, nhưng nó không được đảm bảo thực sự có giá trị đó [1] và trừ khi trình biên dịch của bạn thực hiện hằng số dưới dạng một ký tự #define(nhiều người không, vì NULL thực sự không nên có ý nghĩa trong một phi bối cảnh con trỏ), do đó, mã mở rộng không được đảm bảo thực sự liên quan đến giá trị 0 (mặc dù nó gây nhầm lẫn không liên quan đến glyph bằng 0).

Nếu NULLđược gõ, nó cũng sẽ không có kích thước 1 (hoặc kích thước ký tự khác). Điều này có thể hình dung có thể gây ra các vấn đề bổ sung, mặc dù hầu hết các hằng ký tự thực tế không có kích thước ký tự.

Bây giờ hầu hết mọi người sẽ thấy điều này và nghĩ, "con trỏ null là bất cứ thứ gì khác ngoài tất cả các bit không? Điều gì là vô nghĩa" - nhưng các giả định như thế chỉ an toàn trên các nền tảng phổ biến như x86. Vì bạn đã đề cập rõ ràng mối quan tâm đến việc nhắm mục tiêu các nền tảng khác, bạn cần tính đến vấn đề này, vì bạn đã tách biệt rõ ràng mã của mình khỏi các giả định về bản chất của mối quan hệ giữa con trỏ và số nguyên.

Do đó, trong khi các chuỗi C bị chấm dứt null, chúng không bị chấm dứt bởi NULL, nhưng bởi NUL(thường được viết '\0'). Mã được sử dụng rõ ràng NULLnhư một bộ kết thúc chuỗi sẽ hoạt động trên các nền tảng có cấu trúc địa chỉ đơn giản và thậm chí sẽ biên dịch với nhiều trình biên dịch, nhưng nó hoàn toàn không đúng C.


[1] giá trị con trỏ null thực tế được trình biên dịch chèn vào khi nó đọc 0 mã thông báo trong ngữ cảnh nơi nó sẽ được chuyển đổi thành loại con trỏ. Đây không phải là một chuyển đổi từ giá trị số nguyên 0 và không được đảm bảo giữ nếu có bất kỳ thứ gì ngoài 0chính mã thông báo được sử dụng, chẳng hạn như giá trị động từ một biến; chuyển đổi cũng không thể đảo ngược và con trỏ null không phải mang lại giá trị 0 khi được chuyển đổi thành số nguyên.


Điểm tuyệt vời. Tôi đã gửi một chỉnh sửa để giúp làm rõ điều này.
Monty Harder

" NULđược đảm bảo có giá trị nguyên bằng không." -> C không xác định NUL. Thay vào đó, C xác định rằng các chuỗi có bộ lọc null cuối cùng , một byte với tất cả các bit được đặt thành 0.
chux - Phục hồi lại

1

Tôi đã sử dụng chuỗi trong C, nó có nghĩa là các ký tự kết thúc null được gọi là Chuỗi.

Nó sẽ không có bất kỳ vấn đề nào khi bạn sử dụng trong baremetal hoặc trong bất kỳ hệ điều hành nào như Windows, Linux, RTOS: (FreeRTO, OSE).

Trong thế giới nhúng, chấm dứt null thực sự giúp nhiều mã thông báo dưới dạng chuỗi.

Tôi đã sử dụng các chuỗi trong C như thế trong nhiều hệ thống quan trọng an toàn.

Bạn có thể tự hỏi, chuỗi thực sự trong C là gì?

Các chuỗi kiểu C, là các mảng, cũng có các chuỗi ký tự, chẳng hạn như "này". Trong thực tế, cả hai loại chuỗi này chỉ đơn thuần là bộ sưu tập các ký tự ngồi cạnh nhau trong bộ nhớ.

Bất cứ khi nào bạn viết một chuỗi, được đặt trong dấu ngoặc kép, C sẽ tự động tạo một mảng các ký tự cho chúng tôi, chứa chuỗi đó, được chấm dứt bởi ký tự \ 0.

Ví dụ: bạn có thể khai báo và định nghĩa một mảng các ký tự và khởi tạo nó bằng hằng chuỗi:

char string[] = "Hello cruel world!";

Câu trả lời đơn giản: Bạn không thực sự cần phải lo lắng về việc sử dụng các ký tự bị chấm dứt null, công việc này độc lập với bất kỳ nền tảng nào.


Cảm ơn, không biết rằng khi khai báo với dấu ngoặc kép, a NULsẽ tự động được thêm vào.
Snoop

1

Như những người khác đã nói, chấm dứt null là khá phổ biến đối với tiêu chuẩn C. Nhưng (như những người khác cũng đã chỉ ra) không phải 100%. Ví dụ (khác), hệ điều hành VMS thường sử dụng cái gọi là "mô tả chuỗi" http://h41379.www4.hpe.com/commIAL/c/docs/5492p012.html được truy cập trong C bởi #include <descrip.h >

Công cụ ở cấp ứng dụng có thể sử dụng kết thúc null hoặc không, tuy nhiên nhà phát triển thấy phù hợp. Nhưng các công cụ VMS cấp thấp hoàn toàn yêu cầu các mô tả, hoàn toàn không sử dụng kết thúc null (xem liên kết ở trên để biết chi tiết). Điều này phần lớn là để tất cả các ngôn ngữ (C, lắp ráp, v.v.) trực tiếp sử dụng nội bộ VMS có thể có giao diện chung với chúng.

Vì vậy, nếu bạn dự đoán bất kỳ tình huống tương tự nào, bạn có thể muốn cẩn thận hơn một chút so với "chấm dứt null phổ quát" có thể đề xuất là cần thiết. Tôi sẽ cẩn thận hơn nếu tôi đang làm những gì bạn đang làm, nhưng đối với những thứ ở cấp ứng dụng của tôi thì an toàn khi giả sử chấm dứt null. Tôi sẽ không đề nghị cùng một mức độ an toàn cho bạn. Mã của bạn có thể phải giao tiếp với lắp ráp và / hoặc khác, mã ngôn ngữ tại một số điểm trong tương lai, có thể không phải lúc nào cũng tuân thủ tiêu chuẩn C của các chuỗi kết thúc null.


Hôm nay, 0 chấm dứt thực sự là khá bất thường. C ++ std :: string không, Chuỗi Java không, Objective-C NSString không, Chuỗi Swift không - kết quả là, mỗi thư viện ngôn ngữ hỗ trợ các chuỗi có mã NUL bên trong chuỗi (không thể có với C chuỗi vì lý do rõ ràng).
gnasher729

@ gnasher729 Tôi đã thay đổi "... khá phổ quát" thành "khá phổ quát cho tiêu chuẩn C", mà tôi hy vọng sẽ loại bỏ bất kỳ sự mơ hồ nào và vẫn đúng cho đến ngày hôm nay (theo ý tôi và chủ đề của OP).
John Forkosh

0

Theo kinh nghiệm của tôi về các hệ thống nhúng thời gian thực và quan trọng về an toàn, không có gì lạ khi sử dụng cả hai quy ước chuỗi C và PASCAL, tức là cung cấp độ dài chuỗi làm ký tự đầu tiên, (giới hạn độ dài là 255) và để kết thúc chuỗi có ít nhất một 0x00, ( NUL), làm giảm kích thước có thể sử dụng xuống còn 254.

Một lý do cho điều này là để biết bạn mong đợi bao nhiêu dữ liệu sau khi nhận được byte đầu tiên và một lý do khác là trong các hệ thống như vậy, kích thước bộ đệm động được tránh khi có thể - phân bổ kích thước bộ đệm 256 cố định sẽ nhanh hơn và an toàn hơn (không cần kiểm tra nếu mallocthất bại). Một điều nữa là các hệ thống khác mà bạn đang liên lạc có thể không được viết bằng ANSI-C.

Trong mọi công việc nhúng, điều quan trọng là phải thiết lập và duy trì Tài liệu điều khiển giao diện, (IDC), xác định tất cả các cấu trúc truyền thông của bạn bao gồm định dạng chuỗi, độ bền, kích thước số nguyên, v.v., càng sớm càng tốt, ( lý tưởng trước khi bắt đầu ), và nó phải là của bạn và tất cả các đội, cuốn sách thần thánh khi viết hệ thống - nếu ai đó muốn giới thiệu một cấu trúc hoặc định dạng mới, nó phải được ghi lại ở đó trước và mọi người có thể bị ảnh hưởng thông báo, có thể với tùy chọn phủ quyết thay đổi .

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.