Sự khác biệt giữa mảng char và con trỏ char trong C là gì?


216

Tôi đang cố gắng để hiểu con trỏ trong C nhưng hiện tại tôi đang bối rối với những điều sau đây:

  • char *p = "hello"

    Đây là một con trỏ char trỏ vào mảng ký tự, bắt đầu từ h .

  • char p[] = "hello"

    Đây là một mảng lưu trữ xin chào .

Sự khác biệt khi tôi chuyển cả hai biến này vào hàm này là gì?

void printSomething(char *p)
{
    printf("p: %s",p);
}

5
Điều này sẽ không hợp lệ: char p[3] = "hello";Chuỗi khởi tạo quá dài so với kích thước của mảng bạn khai báo. Typo?
Cody Grey

16
Hoặc chỉ là char p[]="hello";đủ!
Deepdive


1
có thể trùng lặp về sự khác biệt giữa char s [] và char * s trong C là gì? Đúng, điều này cũng hỏi cụ thể về tham số chức năng, nhưng điều đó không charcụ thể.
Ciro Santilli 郝海东 冠状 病 事件

1
bạn cần hiểu chúng về cơ bản là khác nhau. Điểm chung duy nhất ở đây là cơ sở của arry p [] là một con trỏ const cho phép truy cập vào mảng p [] thông qua một con trỏ. p [] tự giữ bộ nhớ cho một chuỗi, trong khi * p chỉ trỏ đến địa chỉ của phần tử đầu tiên chỉ MỘT CHAR (nghĩa là, trỏ đến cơ sở của chuỗi đã được phân bổ). Để minh họa rõ hơn điều này, hãy xem xét bên dưới: char * cPtr = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> đây là một lỗi, vì cPtr là một con trỏ chỉ một ký tự char cBuff [] = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> Đây là Ok, bcos cBuff tự nó là một mảng char
Ilavaraan

Câu trả lời:


222

char*char[] là các loại khác nhau , nhưng nó không rõ ràng ngay lập tức trong mọi trường hợp. Điều này là do các mảng phân rã thành các con trỏ , nghĩa là nếu một biểu thức kiểu char[]được cung cấp trong đó một loại char*được mong đợi, trình biên dịch sẽ tự động chuyển mảng thành một con trỏ thành phần tử đầu tiên.

Hàm ví dụ của bạn printSomethingmong đợi một con trỏ, vì vậy nếu bạn cố gắng truyền một mảng cho nó như thế này:

char s[10] = "hello";
printSomething(s);

Trình biên dịch giả vờ rằng bạn đã viết này:

char s[10] = "hello";
printSomething(&s[0]);

Là một cái gì đó thay đổi từ năm 2012 đến nay. Đối với một mảng ký tự "s" in toàn bộ mảng .. tức là "xin chào"
Bhanu Tez

@BhanuTez Không, cách lưu trữ dữ liệu và những gì được thực hiện với dữ liệu là những mối quan tâm riêng biệt. Ví dụ này in toàn bộ chuỗi vì đó là cách printfxử lý %schuỗi định dạng: bắt đầu tại địa chỉ được cung cấp và tiếp tục cho đến khi gặp bộ kết thúc null. %cVí dụ, nếu bạn muốn in chỉ một ký tự, bạn có thể sử dụng chuỗi định dạng.
iX3

Chỉ muốn hỏi liệu char *p = "abc";ký tự NULL \0có được tự động thêm vào như trong trường hợp mảng char [] không?
KPMG

Tại sao tôi có thể thiết lập char *name; name="123";nhưng có thể làm tương tự với intloại? Và sau khi sử dụng %cđể in name, đầu ra là chuỗi không thể đọc được : ?
TomSawyer

83

Hãy xem nào:

#include <stdio.h>
#include <string.h>

int main()
{
    char *p = "hello";
    char q[] = "hello"; // no need to count this

    printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64
    printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both

    // size_t strlen(const char *s) and we don't get any warnings here:
    printf("%zu\n", strlen(p)); // => 5
    printf("%zu\n", strlen(q)); // => 5

    return 0;
}

foo * và foo [] là các loại khác nhau và chúng được xử lý khác nhau bởi trình biên dịch (con trỏ = địa chỉ + biểu diễn của loại con trỏ, mảng = con trỏ + độ dài tùy chọn của mảng, ví dụ, nếu mảng được phân bổ tĩnh ), các chi tiết có thể được tìm thấy trong tiêu chuẩn. Và ở mức độ thời gian chạy, không có sự khác biệt giữa chúng (trong trình biên dịch chương trình, tốt, hầu như, xem bên dưới).

Ngoài ra, có một câu hỏi liên quan trong C FAQ :

Q : sự khác biệt giữa các khởi tạo này là gì?

char a[] = "string literal";   
char *p  = "string literal";   

Chương trình của tôi gặp sự cố nếu tôi cố gán giá trị mới cho p [i].

A : Một chuỗi ký tự (thuật ngữ chính thức cho một chuỗi trích dẫn kép trong nguồn C) có thể được sử dụng theo hai cách hơi khác nhau:

  1. Là trình khởi tạo cho một mảng char, như trong khai báo char a [], nó chỉ định các giá trị ban đầu của các ký tự trong mảng đó (và, nếu cần, kích thước của nó).
  2. Bất cứ nơi nào khác, nó biến thành một mảng ký tự không tên, tĩnh và mảng không tên này có thể được lưu trữ trong bộ nhớ chỉ đọc và do đó không nhất thiết phải được sửa đổi. Trong ngữ cảnh biểu thức, mảng được chuyển đổi cùng một lúc thành một con trỏ, như thường lệ (xem phần 6), do đó, khai báo thứ hai khởi tạo p để trỏ đến phần tử đầu tiên của mảng không tên.

Một số trình biên dịch có một công tắc kiểm soát xem các chuỗi ký tự có thể ghi được hay không (để biên dịch mã cũ) và một số trình biên dịch có thể có các tùy chọn để các chuỗi ký tự được xử lý chính thức như các mảng const char (để bắt lỗi tốt hơn).

Xem thêm câu hỏi 1.31, 6.1, 6.2, 6.8 và 11.8b.

Tài liệu tham khảo: K & R2 Giây. 5,5 tr. 104

ISO giây. 6.1.4, giây. 6.5.7

Cơ sở lý luận 3.1.4

H & S giây. 2.7.4 trang 31-2


Trong sizeof (q), tại sao q không phân rã thành một con trỏ, như @Jon đề cập trong câu trả lời của anh ấy?
garyp

@ÿp q không phân rã thành một con trỏ vì sizeof là toán tử, không phải là hàm (ngay cả khi sizeof là hàm, q sẽ chỉ phân rã nếu hàm đang mong đợi một con trỏ char).
GiriB

cảm ơn, nhưng printf ("% u \ n" thay vì printf ("% zu \ n", tôi nghĩ bạn nên xóa z.
Zakaria

33

Sự khác biệt giữa mảng char và con trỏ char trong C là gì?

Dự thảo C99 N1256

Có hai cách sử dụng khác nhau của chuỗi ký tự:

  1. Khởi tạo char[]:

    char c[] = "abc";      

    Đây là "nhiều phép thuật hơn" và được mô tả tại 6.7.8 / 14 "Khởi tạo":

    Một mảng các kiểu ký tự có thể được khởi tạo bởi một chuỗi ký tự bằng chữ, được tùy ý đặt trong dấu ngoặc nhọn. Các ký tự liên tiếp của chuỗi ký tự bằng chữ (bao gồm ký tự null kết thúc nếu có chỗ hoặc nếu mảng có kích thước không xác định) khởi tạo các phần tử của mảng.

    Vì vậy, đây chỉ là một phím tắt cho:

    char c[] = {'a', 'b', 'c', '\0'};

    Giống như bất kỳ mảng thông thường khác, ccó thể được sửa đổi.

  2. Ở mọi nơi khác: nó tạo ra một:

    Vì vậy, khi bạn viết:

    char *c = "abc";

    Điều này tương tự như:

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;

    Lưu ý diễn viên ngầm từ char[]đếnchar * , luôn luôn hợp pháp.

    Sau đó, nếu bạn sửa đổi c[0], bạn cũng sửa đổi__unnamed , đó là UB.

    Điều này được ghi lại ở 6.4.5 "Chuỗi ký tự":

    5 Trong giai đoạn dịch 7, một byte hoặc mã có 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ữ. Chuỗi ký tự đa bào sau đó được sử dụng để khởi tạo một mảng thời lượng và độ dài lưu trữ tĩnh vừa đủ để chứa chuỗi. Đối với các ký tự chuỗi ký tự, các thành phần mảng có kiểu char và được khởi tạo với các byte riêng lẻ của chuỗi ký tự đa dòng [...]

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

6.7.8 / 32 "Khởi tạo" đưa ra một ví dụ trực tiếp:

VÍ DỤ 8: Tuyên bố

char s[] = "abc", t[3] = "abc";

định nghĩa các đối tượng mảng char "plain" st có các phần tử được khởi tạo bằng chuỗi ký tự.

Tuyên bố này là giống hệt với

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

Nội dung của các mảng có thể sửa đổi. Mặt khác, tuyên bố

char *p = "abc";

định nghĩa pvới kiểu "con trỏ tới char" và khởi tạo nó để trỏ đến một đối tượng có kiểu "mảng char" có độ dài 4 có các phần tử được khởi tạo với một chuỗi ký tự bằng chữ. Nếu một nỗ lực được thực hiện để sử dụng pđể sửa đổi nội dung của mảng, hành vi không được xác định.

Triển khai ELF GCC 4.8 x86-64

Chương trình:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

Biên dịch và dịch ngược:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

Đầu ra chứa:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

Kết luận: GCC lưu trữ char*nó trong .rodataphần, không phải trong .text.

Nếu chúng ta làm tương tự cho char[]:

 char s[] = "abc";

chúng tôi đạt được:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

vì vậy nó được lưu trữ trong ngăn xếp (liên quan đến %rbp ).

Tuy nhiên, lưu ý rằng tập lệnh liên kết mặc định đặt .rodata.texttrong cùng một phân đoạn, đã thực thi nhưng không có quyền ghi. Điều này có thể được quan sát với:

readelf -l a.out

trong đó có:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

2
@ leszek.hanusz Không xác định hành vi stackoverflow.com/questions/2766731/ Nhật Google "Ngôn ngữ C UB" ;-)
Ciro Santilli 冠状 病 六四 事件 法轮功

9

Bạn không được phép thay đổi nội dung của hằng chuỗi, đây là pđiểm đầu tiên . Thứ hai plà một mảng được khởi tạo với hằng chuỗi và bạn có thể thay đổi nội dung của nó.


6

Đối với các trường hợp như thế này, hiệu ứng là như nhau: Cuối cùng, bạn chuyển địa chỉ của ký tự đầu tiên trong một chuỗi các ký tự.

Các tuyên bố rõ ràng là không giống nhau mặc dù.

Sau đây đặt bộ nhớ sang một bên cho một chuỗi và cũng là một con trỏ ký tự, sau đó khởi tạo con trỏ để trỏ đến ký tự đầu tiên trong chuỗi.

char *p = "hello";

Trong khi các bộ nhớ sau chỉ dành bộ nhớ cho chuỗi. Vì vậy, nó thực sự có thể sử dụng ít bộ nhớ hơn.

char p[10] = "hello";

codeplusplus.blogspot.com/2007/09/ "" Tuy nhiên, việc khởi tạo biến cần một hiệu suất rất lớn và hình phạt không gian cho mảng "
leef 10/03/13

@leef: Tôi nghĩ rằng nó phụ thuộc vào vị trí của biến. Nếu nó nằm trong bộ nhớ tĩnh, tôi nghĩ có thể lưu trữ mảng và dữ liệu trong hình ảnh EXE và không yêu cầu bất kỳ khởi tạo nào cả. Mặt khác, vâng, chắc chắn có thể chậm hơn nếu dữ liệu phải được phân bổ và sau đó dữ liệu tĩnh phải được sao chép.
Jonathan Wood

3

Theo như tôi có thể nhớ, một mảng thực sự là một nhóm các con trỏ. Ví dụ

p[1]== *(&p+1)

là một tuyên bố đúng


2
Tôi sẽ mô tả một mảng như là một con trỏ đến địa chỉ của một khối bộ nhớ. Do đó tại sao *(arr + 1)đưa bạn đến thành viên thứ hai của arr. Nếu *(arr)trỏ đến một địa chỉ bộ nhớ 32 bit, ví dụ bfbcdf5e, sau đó *(arr + 1)trỏ đến bfbcdf60(byte thứ hai). Do đó, tại sao việc đi ra khỏi phạm vi của một mảng sẽ dẫn đến kết quả kỳ lạ nếu HĐH không segfault. Nếu int a = 24;đang ở địa chỉ bfbcdf62, thì việc truy cập arr[2]có thể quay trở lại 24, giả sử segfault không xảy ra trước.
Braden hay nhất

3

Từ APUE , Mục 5.14:

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/

... Đối với mẫu đầu tiên, tên được phân bổ trên ngăn xếp, vì chúng tôi sử dụng một biến mảng. Đối với tên thứ hai, tuy nhiên, chúng tôi sử dụng một con trỏ. Trong trường hợp này, chỉ bộ nhớ cho chính con trỏ nằm trên ngăn xếp; trình biên dịch sắp xếp cho chuỗi được lưu trữ trong phân đoạn chỉ đọc của tệp thực thi. Khi mkstemphàm cố gắng sửa đổi chuỗi, một lỗi phân đoạn xảy ra.

Văn bản được trích dẫn phù hợp với lời giải thích của @Ciro Santilli.


1

char p[3] = "hello"? nên làchar p[6] = "hello" nhớ có một '\ 0' char ở cuối "chuỗi" trong C.

dù sao, mảng trong C chỉ là một con trỏ đến đối tượng đầu tiên của một đối tượng điều chỉnh trong bộ nhớ. chỉ khác nhau là trong ngữ nghĩa. trong khi bạn có thể thay đổi giá trị của một con trỏ để trỏ đến một vị trí khác trong bộ nhớ, một mảng, sau khi được tạo, sẽ luôn trỏ đến cùng một vị trí.
Ngoài ra, khi sử dụng mảng, "mới" và "xóa" sẽ tự động được thực hiện cho bạn.

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.