“Thời gian tồn tại” của một chuỗi ký tự trong C


84

Con trỏ được trả về bởi hàm sau sẽ không thể truy cập được?

char *foo(int rc)
{
    switch (rc)
    {
        case 1:

            return("one");

        case 2:

            return("two");

        default:

            return("whatever");
    }
}

Vì vậy, thời gian tồn tại của một biến cục bộ trong C / C ++ thực tế chỉ nằm trong hàm, phải không? Có nghĩa là, sau khi char* foo(int)kết thúc, con trỏ nó trả về không còn ý nghĩa gì nữa, phải không?

Tôi hơi bối rối về thời gian tồn tại của một biến cục bộ. Làm rõ tốt là gì?


10
"Var" duy nhất bạn có trong hàm của mình là tham số int rc. Thời gian tồn tại của nó kết thúc ở mỗi dấu return-s. Các con trỏ bạn đang trả về là các ký tự chuỗi. Các chuỗi ký tự có thời lượng lưu trữ tĩnh: thời gian tồn tại của chúng ít nhất bằng thời gian của chương trình.
Kaz

14
@PedroAlves Tại sao không? Các phương thức cho phép trừu tượng hóa; Điều gì sẽ xảy ra nếu trong tương lai, chuỗi sẽ được đọc từ một tài nguyên dịch, nhưng không cần hỗ trợ quốc tế hóa cho V1 (hoặc V0.5) của một sản phẩm?
dlev

1
@PedroAlves "Mã của bạn chắc chắn sẽ hoạt động (và bạn có thể thấy nó nếu cố gắng biên dịch)," Điều đó không tuân theo. Nhiều (hầu hết? Về cơ bản là mọi trình biên dịch?) C sẽ sử dụng mã bất hợp pháp và thường phát ra mã có vẻ hoạt động. Nhưng hãy thử nó trong một trình biên dịch khác (hoặc thậm chí là một phiên bản khác của cùng một trình biên dịch) và nó có thể bị rơi.
dmckee --- ex-moderator kitten

6
@PedroAlves, một hàm trả về một chuỗi hằng số có thể được sử dụng hạn chế, nhưng còn hàm trả về bất kỳ một trong số các chuỗi hằng số tùy thuộc vào trạng thái đầu vào hoặc đối tượng thì sao? Một ví dụ đơn giản sẽ là một hàm để chuyển đổi một kiểu liệt kê thành biểu diễn chuỗi của nó.
Mark Ransom

4
strerrorRõ ràng là bạn chưa bao giờ thấy chức năng này.
Kaz

Câu trả lời:


86

Đúng, thời gian tồn tại của một biến cục bộ nằm trong phạm vi ( {, }) mà nó được tạo.

Biến cục bộ có lưu trữ tự động hoặc cục bộ. Tự động vì chúng tự động bị hủy khi phạm vi mà chúng được tạo kết thúc.

Tuy nhiên, những gì bạn có ở đây là một chuỗi ký tự, được cấp phát trong một bộ nhớ chỉ đọc được xác định thực thi. Các ký tự chuỗi khác với các biến cục bộ và chúng vẫn tồn tại trong suốt vòng đời của chương trình. Chúng có thời lượng tĩnh [Tham chiếu 1] .

Một lời cảnh báo!

Tuy nhiên, lưu ý rằng bất kỳ nỗ lực nào để sửa đổi nội dung của một chuỗi ký tự là một hành vi không xác định (UB). Chương trình người dùng không được phép sửa đổi nội dung của một chuỗi ký tự.
Do đó, nó luôn được khuyến khích sử dụng consttrong khi khai báo một chuỗi ký tự.

const char*p = "string"; 

thay vì,

char*p = "string";    

Trên thực tế, trong C ++, việc khai báo một chuỗi ký tự mà không có ký tự constmặc dù không có trong C. Tuy nhiên, khai báo một chuỗi ký tự với a constmang lại cho bạn lợi thế là các trình biên dịch thường đưa ra cảnh báo cho bạn trong trường hợp bạn cố gắng sửa đổi chuỗi ký tự trong trường hợp thứ hai.

Chương trình mẫu :

#include<string.h> 
int main() 
{ 
    char *str1 = "string Literal"; 
    const char *str2 = "string Literal"; 
    char source[]="Sample string"; 
 
    strcpy(str1,source);    // No warning or error just Uundefined Behavior 
    strcpy(str2,source);    // Compiler issues a warning 
 
    return 0; 
} 

Đầu ra:

cc1: cảnh báo được coi là lỗi
prog.c: Trong hàm 'main':
prog.c: 9: error: truyền đối số 1 của 'strcpy' loại bỏ các định nghĩa khỏi loại đích con trỏ

Lưu ý rằng trình biên dịch cảnh báo cho trường hợp thứ hai, nhưng không cảnh báo cho trường hợp đầu tiên.


Để trả lời câu hỏi đang được hỏi bởi một vài người dùng tại đây:

Đối phó với các ký tự tích phân là gì?

Nói cách khác, đoạn mã sau có hợp lệ không?

int *foo()
{
    return &(2);
} 

Câu trả lời là, không mã này không hợp lệ. Nó không hợp lệ và sẽ gây ra lỗi trình biên dịch.

Cái gì đó như:

prog.c:3: error: lvalue required as unary ‘&’ operand
     

Các ký tự chuỗi là giá trị l, nghĩa là: Bạn có thể lấy địa chỉ của một ký tự chuỗi, nhưng không thể thay đổi nội dung của nó.
Tuy nhiên, bất kỳ literals khác ( int, float, char, vv) là r-giá trị (tiêu chuẩn C sử dụng thuật ngữ giá trị của một biểu thức cho các) và địa chỉ của họ không thể được thực hiện ở tất cả.


[Tham khảo 1] Tiêu chuẩn C99 6.4.5 / 5 "Chữ viết chuỗi - Ngữ nghĩa":

Trong giai đoạn dịch 7, một byte hoặc mã có giá trị 0 được nối vào mỗi chuỗi ký tự đa byte là kết quả của một chuỗi ký tự hoặc ký tự. Sau đó, chuỗi ký tự multibyte được sử dụng để khởi tạo một mảng lưu trữ tĩnh và độ dài vừa đủ để chứa chuỗi . Đối với các ký tự chuỗi ký tự, các phần tử mảng có kiểu char, và được khởi tạo bằng các byte riêng lẻ của chuỗi ký tự nhiều byte; đối với các ký tự chuỗi rộng, các phần tử mảng có kiểu wchar_t và được khởi tạo bằng chuỗi các ký tự rộng ...

Không xác định được liệu các mảng này có khác biệt hay không miễn là các phần tử của chúng có giá trị thích hợp. Nếu chương trình cố gắng sửa đổi một mảng như vậy, hành vi là không xác định .


Điều gì sẽ xảy ra nếu người dùng trả lại một số thứ như thế này. char * a = & "abc"; trả lại a; Điều này sẽ không hợp lệ?
Ashwin

@Ashwin: Loại của chuỗi theo nghĩa đen là char (*)[4]. Điều này là do, kiểu "abc"char[4]và con trỏ đến một mảng 4 ký tự được khai báo là char (*)[4], Vì vậy, Nếu bạn cần lấy địa chỉ của nó, Bạn cần làm như char (*a)[4] = &"abc";và Có, nó hợp lệ.
Alok Save

@Als "abc" là char[4]. (Bởi vì '\0')
asaelr

1
Có lẽ cũng nên cảnh báo rằng điều char const s[] = "text";đó không tạo nên smột ký tự theo nghĩa đen, và do đó s sẽ bị phá hủy ở cuối phạm vi, vì vậy bất kỳ con trỏ nào còn sống sót đến nó sẽ treo lơ lửng.
celtschk

1
@celtschk: Tôi rất thích, nhưng câu hỏi Q đặc biệt là về chuỗi ký tự Vì vậy, tôi sẽ bám sát chủ đề này. Tuy nhiên, đối với những người quan tâm câu trả lời của tôi ở đây, Sự khác biệt giữa char a [] = “string” và char là gì * p = "chuỗi"? nên khá hữu ích.
Alok Save

74

Nó hợp lệ. Chuỗi ký tự có thời lượng lưu trữ tĩnh, vì vậy con trỏ không bị treo.

Đối với C, điều đó được quy định trong phần 6.4.5, đoạn 6:

Trong giai đoạn dịch 7, một byte hoặc mã có giá trị 0 được nối vào mỗi chuỗi ký tự nhiều byte là kết quả của một chuỗi ký tự hoặc các ký tự. Sau đó, chuỗi ký tự multibyte được sử dụng để khởi tạo một mảng lưu trữ tĩnh và độ dài vừa đủ để chứa chuỗi.

Và đối với C ++ trong phần 2.14.5, đoạn 8-11:

8 Các ký tự chuỗi thông thường và các ký tự chuỗi UTF-8 cũng được gọi là các ký tự chuỗi hẹp. Một ký tự chuỗi hẹp có kiểu “mảng n const char”, trong đó n là kích thước của chuỗi như được định nghĩa bên dưới và có thời lượng lưu trữ tĩnh (3.7).

9 Một ký tự chuỗi bắt đầu bằng u, chẳng hạn u"asdf", là một char16_tký tự chuỗi. Một char16_tchuỗi ký tự có kiểu “mảng của n const char16_t”, trong đó n là kích thước của chuỗi như được định nghĩa bên dưới; nó có thời lượng lưu trữ tĩnh và được khởi tạo với các ký tự đã cho. Một c-char đơn lẻ có thể tạo ra nhiều hơn một char16_tký tự ở dạng các cặp thay thế.

10 Một ký tự chuỗi bắt đầu bằng U, chẳng hạn như U"asdf", là một char32_tký tự chuỗi. Một char32_tchuỗi ký tự có kiểu “mảng của n const char32_t”, trong đó n là kích thước của chuỗi như được định nghĩa bên dưới; nó có thời lượng lưu trữ tĩnh và được khởi tạo với các ký tự đã cho.

11 Một ký tự chuỗi bắt đầu bằng L, chẳng hạn L"asdf", là một ký tự chuỗi rộng. Một chuỗi ký tự rộng có kiểu “mảng n const wchar_t”, trong đó n là kích thước của chuỗi như được định nghĩa bên dưới; nó có thời lượng lưu trữ tĩnh và được khởi tạo với các ký tự đã cho.


FYI: Câu trả lời này đã được sáp nhập từ stackoverflow.com/questions/16470959/...
Shog9

14

Các ký tự chuỗi có giá trị cho toàn bộ chương trình (và không được cấp phát không phải là ngăn xếp), vì vậy nó sẽ hợp lệ.

Ngoài ra, các ký tự chuỗi là chỉ đọc, vì vậy (để có phong cách tốt) có thể bạn nên thay đổi foothànhconst char *foo(int)


Điều gì sẽ xảy ra nếu người dùng trả lại một số thứ như thế này. char * a = & "abc"; trả lại a; Điều này sẽ không hợp lệ?
Ashwin

&"abc"không char*. đó là một địa chỉ của mảng và kiểu của nó là char(*)[4]. Tuy nhiên, một trong hai return &"abc";char *a="abc";return a;đều hợp lệ.
asaelr

@asaelr: Thực ra, nó không chỉ là vì mục đích cho một văn phong tốt , hãy kiểm tra câu trả lời của tôi để biết chi tiết.
Alok Save

@Als Chà, nếu anh ấy viết toàn bộ chương trình, anh ấy có thể tránh thay đổi chuỗi mà không viết const, và nó sẽ hoàn toàn hợp pháp, nhưng nó vẫn còn tệ.
asaelr

nếu nó hợp lệ cho toàn bộ chương trình, tại sao chúng ta cần phân bổ nó?
TomSawyer

7

Có, nó là mã hợp lệ, xem trường hợp 1 bên dưới. Bạn có thể trả về chuỗi C một cách an toàn từ một hàm theo ít nhất những cách sau:

  • const char*thành một chuỗi ký tự. Nó không thể được sửa đổi và không được giải phóng bởi người gọi. Nó hiếm khi hữu ích cho mục đích trả về giá trị mặc định, vì sự cố giải phóng được mô tả bên dưới. Nó có thể có ý nghĩa nếu bạn thực sự cần chuyển một con trỏ hàm ở đâu đó, vì vậy bạn cần một hàm trả về một chuỗi ..

  • char*hoặc const char*vào một bộ đệm char tĩnh. Nó không được giải phóng bởi người gọi. Nó có thể được sửa đổi (hoặc bởi người gọi nếu không phải là const hoặc bởi hàm trả về nó), nhưng một hàm trả về điều này không thể (dễ dàng) có nhiều bộ đệm, vì vậy nó không (dễ dàng) threadsafe và người gọi có thể cần để sao chép giá trị trả về trước khi gọi lại hàm.

  • char*vào bộ đệm được cấp phát với malloc. Nó có thể được sửa đổi, nhưng nó thường phải được giải phóng rõ ràng bởi người gọi và có phí phân bổ heap. strdupthuộc loại này.

  • const char*hoặc char*tới một bộ đệm, được truyền như một đối số cho hàm (con trỏ trả về không cần trỏ đến phần tử đầu tiên của bộ đệm đối số). Nó giao trách nhiệm quản lý bộ đệm / bộ nhớ cho người gọi. Nhiều hàm chuỗi tiêu chuẩn thuộc loại này.

Một vấn đề là, việc trộn chúng trong một chức năng có thể trở nên phức tạp. Người gọi cần biết nó sẽ xử lý con trỏ trả về như thế nào, nó có hiệu lực trong bao lâu và liệu người gọi có nên giải phóng nó hay không và không có cách nào (hay) để xác định điều đó trong thời gian chạy. Vì vậy, bạn không thể, ví dụ, có một hàm, đôi khi trả về một con trỏ đến bộ đệm được phân bổ theo heap mà người gọi cần freevà đôi khi một con trỏ đến giá trị mặc định từ chuỗi ký tự, mà người gọi không được free .


FYI: Câu trả lời này đã được sáp nhập từ stackoverflow.com/questions/16470959/...
Shog9

6

Câu hỏi hay. Nói chung, bạn sẽ đúng, nhưng ví dụ của bạn là ngoại lệ. Trình biên dịch cấp phát tĩnh bộ nhớ chung cho một chuỗi ký tự. Do đó, địa chỉ do hàm của bạn trả về là hợp lệ.

Đó là một tính năng khá tiện lợi của C, phải không? Nó cho phép một hàm trả về một thông điệp được soạn sẵn mà không buộc người lập trình phải lo lắng về bộ nhớ mà thông điệp được lưu trữ.

Xem thêm nhận xét đúng của @ asaelr re const.


: Điều gì sẽ xảy ra nếu người dùng trả lại một số thứ như thế này. char * a = & "abc"; trả lại a; Điều này sẽ không hợp lệ?
Ashwin

Đúng. Trên thực tế, người ta có thể chỉ cần viết const char *a = "abc";, bỏ qua &. Lý do là một chuỗi được trích dẫn kép phân giải thành địa chỉ của ký tự ban đầu của nó.
thb

3

Các biến cục bộ chỉ hợp lệ trong phạm vi chúng được khai báo, tuy nhiên bạn không khai báo bất kỳ biến cục bộ nào trong hàm đó.

Hoàn toàn hợp lệ khi trả về một con trỏ đến một chuỗi ký tự từ một hàm, vì một ký tự chuỗi tồn tại trong toàn bộ quá trình thực thi chương trình, giống như một statichoặc một biến toàn cục.

Nếu bạn lo lắng về những gì bạn đang làm có thể không hợp lệ không xác định, bạn nên bật các cảnh báo trình biên dịch của mình để xem liệu thực tế có điều gì bạn đang làm sai hay không.


Điều gì sẽ xảy ra nếu người dùng trả lại một số thứ như thế này. char * a = & "abc"; trả lại a; Điều này sẽ không hợp lệ?
Ashwin

@Ashwin: &"abc"không thuộc loại char*, tuy nhiên cả hai "abc"&"abc"đều hợp lệ trong toàn bộ quá trình thực thi chương trình.
AusCBloke

2

strsẽ không bao giờ là một con trỏ treo lơ lửng, vì nó trỏ đến một địa chỉ tĩnh nơi chứa các ký tự chuỗi.

Nó sẽ chủ yếu ở chế độ chỉ đọctoàn cục đối với chương trình khi nó được tải.

Ngay cả khi bạn cố gắng giải phóng hoặc sửa đổi, nó sẽ gây ra lỗi phân đoạn trên các nền tảng có bảo vệ bộ nhớ .


FYI: Câu trả lời này đã được sáp nhập từ stackoverflow.com/questions/16470959/...
Shog9

nếu nó sẽ không bao giờ bị lủng lẳng, tôi có cần phải tháo nó ra không? Không?
TomSawyer

0

Một biến cục bộ được cấp phát trên ngăn xếp. Sau khi hàm kết thúc, biến sẽ vượt ra khỏi phạm vi và không thể truy cập được trong mã. Tuy nhiên, nếu bạn có một con trỏ toàn cục (hoặc đơn giản - chưa nằm ngoài phạm vi) mà bạn đã gán để trỏ đến biến đó, nó sẽ trỏ đến vị trí trong ngăn xếp nơi có biến đó. Nó có thể là một giá trị được sử dụng bởi một hàm khác hoặc một giá trị vô nghĩa.


Điều gì sẽ xảy ra nếu người dùng trả lại một số thứ như thế này. char * a = & "abc"; trả lại a; Điều này sẽ không hợp lệ?
Ashwin

0

Trong ví dụ trên được hiển thị bởi bạn, bạn thực sự đang trả lại các con trỏ được cấp phát cho bất kỳ hàm nào gọi hàm trên. Vì vậy, nó sẽ không trở thành một con trỏ cục bộ. Và hơn nữa, đối với các con trỏ cần được trả về, bộ nhớ được cấp phát trong phân đoạn toàn cục.

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.