Không có ý nghĩa gì


541

Vui lòng bao gồm một ví dụ với lời giải thích.


điều này có thể giúp bạn: stackoverflow.com/questions/2795575/ Kẻ
Harry Joy


24
int *p;sẽ định nghĩa một con trỏ tới một số nguyên và *psẽ bỏ qua con trỏ đó, nghĩa là nó thực sự sẽ lấy dữ liệu mà p trỏ tới.
Peyman

4
Con trỏ vui nhộn của Binky ( csl Library.stanford.edu/104 ) là một video TUYỆT VỜI về con trỏ có thể làm rõ mọi thứ. @ Erik- Bạn đá vì đưa lên liên kết Thư viện CS CS Stanford. Có rất nhiều điều tốt đẹp ở đó ...
templatetypedef

6
Phản ứng của Harry là trái ngược với hữu ích ở đây.
Jim Balter

Câu trả lời:


731

Xem lại thuật ngữ cơ bản

thường đủ tốt - trừ khi bạn lập trình lắp ráp - để dự tính một con trỏ chứa địa chỉ bộ nhớ số, với 1 tham chiếu đến byte thứ hai trong bộ nhớ của tiến trình, 2 phần ba, 3 phần tư, v.v.

  • Điều gì đã xảy ra với 0 và byte đầu tiên? Chà, chúng ta sẽ hiểu điều đó sau - xem con trỏ null bên dưới.
  • Để có định nghĩa chính xác hơn về những gì con trỏ lưu trữ, và bộ nhớ và địa chỉ liên quan như thế nào, hãy xem "Tìm hiểu thêm về địa chỉ bộ nhớ và tại sao bạn có thể không cần biết" ở cuối câu trả lời này.

Khi bạn muốn truy cập dữ liệu / giá trị trong bộ nhớ mà con trỏ trỏ tới - các nội dung của địa chỉ với điều đó chỉ số - sau đó bạn dereference con trỏ.

Các ngôn ngữ máy tính khác nhau có các ký hiệu khác nhau để thông báo cho trình biên dịch hoặc trình thông dịch rằng bạn hiện đang quan tâm đến giá trị (hiện tại) của đối tượng trỏ - Tôi tập trung bên dưới vào C và C ++.

Một kịch bản con trỏ

Hãy xem xét trong C, đưa ra một con trỏ như pdưới đây ...

const char* p = "abc";

... bốn byte với các giá trị số được sử dụng để mã hóa các chữ cái 'a', 'b', 'c' và 0 byte để biểu thị phần cuối của dữ liệu văn bản, được lưu trữ ở đâu đó trong bộ nhớ và địa chỉ số của số đó dữ liệu được lưu trữ trong p. Cách này C mã hóa văn bản trong bộ nhớ được gọi là ASCIIZ .

Ví dụ: nếu chuỗi ký tự xảy ra ở địa chỉ 0x1000 và pcon trỏ 32 bit ở 0x2000, nội dung bộ nhớ sẽ là:

Memory Address (hex)    Variable name    Contents
1000                                     'a' == 97 (ASCII)
1001                                     'b' == 98
1002                                     'c' == 99
1003                                     0
...
2000-2003               p                1000 hex

Lưu ý rằng không có tên biến / định danh cho địa chỉ 0x1000, nhưng chúng ta có thể gián tiếp tham chiếu chuỗi ký tự bằng cách sử dụng một con trỏ lưu trữ địa chỉ của nó : p.

Hủy bỏ con trỏ

Để đề cập đến các ký tự được ptrỏ đến, chúng tôi psử dụng một trong các ký hiệu này (một lần nữa, cho C):

assert(*p == 'a');  // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
                     // p and 1 times the size of the things to which p points:
                     // In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b');  // Another notation for p[1]

Bạn cũng có thể di chuyển con trỏ qua dữ liệu trỏ, hủy bỏ chúng khi bạn đi:

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...

Nếu bạn có một số dữ liệu có thể được ghi vào, thì bạn có thể làm những việc như thế này:

int x = 2;
int* p_x = &x;  // Put the address of the x variable into the pointer p_x
*p_x = 4;       // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4

Ở trên, bạn phải biết tại thời điểm biên dịch rằng bạn sẽ cần một biến được gọi xvà mã yêu cầu trình biên dịch sắp xếp nơi cần lưu trữ, đảm bảo địa chỉ sẽ có sẵn thông qua &x.

Dereferences và truy cập một thành viên dữ liệu cấu trúc

Trong C, nếu bạn có một biến là con trỏ tới cấu trúc có các thành viên dữ liệu, bạn có thể truy cập các thành viên đó bằng ->toán tử hội nghị:

typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159;  // Dereference and access data member x.d_
(*p).d_ *= -1;    // Another equivalent notation for accessing x.d_

Kiểu dữ liệu nhiều byte

Để sử dụng một con trỏ, một chương trình máy tính cũng cần có một cái nhìn sâu sắc về loại dữ liệu được trỏ đến - nếu loại dữ liệu đó cần nhiều hơn một byte để biểu diễn, thì con trỏ thường trỏ đến byte được đánh số thấp nhất trong dữ liệu.

Vì vậy, nhìn vào một ví dụ phức tạp hơn một chút:

double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3);  // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4);  // Actually looks at bytes from address p + 1 * sizeof(double)
                       // (sizeof(double) is almost always eight bytes)
++p;                   // Advance p by sizeof(double)
assert(*p == 13.4);    // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8;       // Change sizes[3] from 19.4 to 29.8
                       // Note earlier ++p and + 2 here => sizes[3]

Con trỏ tới bộ nhớ được cấp phát động

Đôi khi bạn không biết mình sẽ cần bao nhiêu bộ nhớ cho đến khi chương trình của bạn chạy và xem dữ liệu nào được ném vào nó ... sau đó bạn có thể tự động phân bổ bộ nhớ bằng cách sử dụng malloc. Đó là thông lệ để lưu trữ địa chỉ trong một con trỏ ...

int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10;            // Dereference the pointer to the memory, then write a value in
fn(*p);             // Call a function, passing it the value at address p
(*p) += 3;          // Change the value, adding 3 to it
free(p);            // Release the memory back to the heap allocation library

Trong C ++, việc cấp phát bộ nhớ thường được thực hiện với newtoán tử và phân bổ bằng delete:

int* p = new int(10); // Memory for one int with initial value 10
delete p;

p = new int[10];      // Memory for ten ints with unspecified initial value
delete[] p;

p = new int[10]();    // Memory for ten ints that are value initialised (to 0)
delete[] p;

Xem thêm C ++ con trỏ thông minh dưới đây.

Mất và rò rỉ địa chỉ

Thường thì một con trỏ có thể là dấu hiệu duy nhất về nơi tồn tại một số dữ liệu hoặc bộ đệm trong bộ nhớ. Nếu cần sử dụng liên tục dữ liệu / bộ đệm đó hoặc khả năng gọi free()hoặc deleteđể tránh rò rỉ bộ nhớ, thì lập trình viên phải thao tác trên một bản sao của con trỏ ...

const char* p = asprintf("name: %s", name);  // Common but non-Standard printf-on-heap

// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
    if (!isprint(*q))
        *q = '_';

printf("%s\n", p); // Only q was modified
free(p);

... hoặc cẩn thận sắp xếp đảo ngược mọi thay đổi ...

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...
free(p);

Con trỏ thông minh C ++

Trong C ++, cách tốt nhất là sử dụng các đối tượng con trỏ thông minh để lưu trữ và quản lý các con trỏ, tự động giải phóng chúng khi các hàm hủy của con trỏ thông minh chạy. Vì C ++ 11, Thư viện tiêu chuẩn cung cấp hai, unique_ptrkhi có một chủ sở hữu duy nhất cho một đối tượng được phân bổ ...

{
    std::unique_ptr<T> p{new T(42, "meaning")};
    call_a_function(p);
    // The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete

... Và shared_ptrđể sở hữu cổ phần (sử dụng tính tham chiếu ) ...

{
    auto p = std::make_shared<T>(3.14, "pi");
    number_storage1.may_add(p); // Might copy p into its container
    number_storage2.may_add(p); // Might copy p into its container    } // p's destructor will only delete the T if neither may_add copied it

Con trỏ rỗng

Trong C, NULL0- và thêm vào C ++ nullptr- có thể được sử dụng để chỉ ra rằng một con trỏ hiện không giữ địa chỉ bộ nhớ của một biến và không nên được quy định hoặc sử dụng trong số học con trỏ. Ví dụ:

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
    switch (c) {
      case f: p_filename = optarg; break;
    }
if (p_filename)  // Only NULL converts to false
    ...   // Only get here if -f flag specified

Trong C và C ++, giống như các kiểu số sẵn có không nhất thiết phải mặc định 0, cũng không boolsphải false, các con trỏ không phải luôn luôn được đặt thành NULL. Tất cả các giá trị này được đặt thành 0 / false / NULL khi chúng là staticbiến hoặc (chỉ C ++) biến thành viên trực tiếp hoặc gián tiếp của các đối tượng tĩnh hoặc cơ sở của chúng hoặc trải qua khởi tạo 0 (ví dụ new T();new T(x, y, z);thực hiện khởi tạo 0 trên các thành viên của T bao gồm cả con trỏ, trong khi new T;không làm).

Hơn nữa, khi bạn gán 0, NULLnullptrđể một con trỏ các bit trong con trỏ không nhất thiết tất cả các thiết lập lại: con trỏ có thể không chứa "0" ở cấp độ phần cứng, hoặc tham khảo địa chỉ 0 trong không gian địa chỉ ảo của bạn. Trình biên dịch được phép lưu trữ cái gì khác ở đó nếu nó có lý do gì để, nhưng bất cứ điều gì nó - nếu bạn đi cùng và so sánh các con trỏ đến 0, NULL, nullptrhoặc một con trỏ đã được giao bất kỳ của những người, công việc so sánh phải như mong đợi. Vì vậy, bên dưới mã nguồn ở cấp trình biên dịch, "NULL" có khả năng hơi "kỳ diệu" trong ngôn ngữ C và C ++ ...

Tìm hiểu thêm về địa chỉ bộ nhớ và lý do có thể bạn không cần biết

Nghiêm khắc hơn, các con trỏ khởi tạo lưu trữ một mẫu bit xác định một NULLhoặc một địa chỉ bộ nhớ (thường là ảo ).

Trường hợp đơn giản là đây là phần bù số vào toàn bộ không gian địa chỉ ảo của quy trình; trong trường hợp phức tạp hơn, con trỏ có thể liên quan đến một số vùng nhớ cụ thể mà CPU có thể chọn dựa trên các thanh ghi "phân đoạn" của CPU hoặc một số cách của id phân đoạn được mã hóa theo mẫu bit và / hoặc tìm ở các vị trí khác nhau tùy thuộc vào hướng dẫn mã máy sử dụng địa chỉ.

Ví dụ, một int*khởi tạo đúng để trỏ đến một intbiến có thể - sau khi truyền tới float*bộ nhớ truy cập trong bộ nhớ "GPU" khá khác biệt với bộ nhớ có intbiến, sau đó một lần được sử dụng như một con trỏ hàm, nó có thể trỏ vào thêm bộ nhớ máy riêng biệt opcodes cho chương trình (với giá trị số của int*con trỏ ngẫu nhiên, không hợp lệ trong các vùng bộ nhớ khác này).

Các ngôn ngữ lập trình 3GL như C và C ++ có xu hướng che giấu sự phức tạp này, như:

  • Nếu trình biên dịch cung cấp cho bạn một con trỏ tới một biến hoặc hàm, bạn có thể tự do hủy bỏ nó (miễn là biến đó không bị phá hủy / giải quyết trong khi đó) và đó là vấn đề của trình biên dịch cho dù là một thanh ghi phân đoạn CPU cụ thể cần được khôi phục trước hay hướng dẫn sử dụng mã máy riêng biệt

  • Nếu bạn nhận được một con trỏ tới một phần tử trong một mảng, bạn có thể sử dụng số học con trỏ để di chuyển bất kỳ nơi nào khác trong mảng hoặc thậm chí để tạo một địa chỉ một đầu cuối của mảng hợp pháp để so sánh với các con trỏ khác với các phần tử trong mảng (hoặc đã được di chuyển tương tự bởi số học con trỏ đến cùng một giá trị một quá khứ); một lần nữa trong C và C ++, tùy thuộc vào trình biên dịch để đảm bảo "chỉ hoạt động"

  • Các chức năng cụ thể của hệ điều hành, ví dụ ánh xạ bộ nhớ dùng chung, có thể cung cấp cho bạn các con trỏ và chúng sẽ "chỉ hoạt động" trong phạm vi địa chỉ có ý nghĩa đối với chúng

  • Nỗ lực di chuyển con trỏ hợp pháp vượt ra ngoài các ranh giới này hoặc chuyển các số tùy ý sang con trỏ hoặc sử dụng các con trỏ chuyển sang các loại không liên quan, thường có hành vi không xác định , vì vậy nên tránh các thư viện và ứng dụng cấp cao hơn, nhưng mã cho HĐH, trình điều khiển thiết bị, v.v. . có thể cần phải dựa vào hành vi không được xác định bởi Tiêu chuẩn C hoặc C ++, tuy nhiên vẫn được xác định rõ bởi việc triển khai hoặc phần cứng cụ thể của chúng.


p[1] *(p + 1) giống hệt nhau ? Đó là, Có p[1] *(p + 1)tạo ra các hướng dẫn tương tự?
Pacerier

2
@Pacerier: từ 6.5.2.1/2 trong bản dự thảo C15 N1570 (lần đầu tiên tôi tìm thấy trực tuyến) "Định nghĩa của toán tử đăng ký [] là E1 [E2] giống hệt với (* ((E1) + (E2)) ). " - Tôi không thể tưởng tượng được bất kỳ lý do nào khiến trình biên dịch sẽ ngay lập tức chuyển đổi chúng thành các biểu diễn giống hệt nhau trong giai đoạn biên dịch ban đầu, áp dụng các tối ưu hóa tương tự sau đó, nhưng tôi không thấy ai có thể chứng minh mã này giống hệt nhau mà không khảo sát mọi trình biên dịch từng được viết.
Tony Delroy

3
@Honey: giá trị 1000 hex quá lớn để mã hóa trong một byte (8 bit) bộ nhớ: bạn chỉ có thể lưu trữ các số không dấu từ 0 đến 255 trong một byte. Vì vậy, bạn không thể lưu trữ 1000 hex tại "chỉ" địa chỉ 2000. Thay vào đó, hệ thống 32 bit sẽ sử dụng 32 bit - tức là bốn byte - với địa chỉ từ 2000 đến 2003. Hệ thống 64 bit sẽ sử dụng 64 bit bit - 8 byte - từ 2000 đến 2007 Dù bằng cách nào, địa chỉ cơ sở của pchỉ là 2000: nếu bạn có một con trỏ khác pthì nó sẽ phải lưu 2000 trong bốn hoặc tám byte. Mong rằng sẽ giúp! Chúc mừng.
Tony Delroy

1
@TonyDelroy: Nếu một liên minh ucó chứa một mảng arr, cả gcc và clang sẽ nhận ra rằng giá trị u.arr[i]có thể truy cập vào cùng một bộ lưu trữ như các thành viên khác trong liên minh, nhưng sẽ không nhận ra rằng lvalue *(u.arr+i)có thể làm như vậy. Tôi không chắc liệu các tác giả của các trình biên dịch đó nghĩ rằng cái sau gọi ra UB hay cái trước gọi ra UB nhưng dù sao họ cũng nên xử lý nó một cách hữu ích, nhưng họ xem rõ hai biểu thức là khác nhau.
supercat

3
Tôi hiếm khi thấy con trỏ và việc sử dụng chúng trong C / C ++ được giải thích chính xác và đơn giản.
kayleeFrye_onDeck

102

Hủy bỏ hội nghị một con trỏ có nghĩa là nhận được giá trị được lưu trữ trong vị trí bộ nhớ được trỏ bởi con trỏ. Toán tử * được sử dụng để làm điều này và được gọi là toán tử hội nghị.

int a = 10;
int* ptr = &a;

printf("%d", *ptr); // With *ptr I'm dereferencing the pointer. 
                    // Which means, I am asking the value pointed at by the pointer.
                    // ptr is pointing to the location in memory of the variable a.
                    // In a's location, we have 10. So, dereferencing gives this value.

// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.

 *ptr = 20;         // Now a's content is no longer 10, and has been modified to 20.

15
Một con trỏ không trỏ đến một giá trị , nó trỏ đến một đối tượng .
Keith Thompson

51
@KeithThndry Một con trỏ không trỏ đến một đối tượng, nó trỏ đến một địa chỉ bộ nhớ, nơi đặt một đối tượng (có thể là nguyên thủy).
mg30rg

4
@ mg30rg: Tôi không chắc bạn đang tạo ra sự khác biệt nào. Một giá trị con trỏ một địa chỉ. Theo định nghĩa, một đối tượng là "vùng lưu trữ dữ liệu trong môi trường thực thi, nội dung có thể biểu thị các giá trị". Và bạn có ý nghĩa gì bởi "nguyên thủy"? Tiêu chuẩn C không sử dụng thuật ngữ đó.
Keith Thompson

6
@KeithThndry Tôi hầu như không chỉ ra rằng, bạn thực sự không thêm giá trị cho câu trả lời, bạn chỉ hiểu về thuật ngữ (và cũng đã làm sai). Giá trị con trỏ chắc chắn là một địa chỉ, đó là cách nó "trỏ" đến một địa chỉ bộ nhớ. Từ "đối tượng" trong thế giới OOPdriven của chúng ta có thể gây hiểu nhầm, bởi vì nó có thể được hiểu là "thể hiện lớp" (vâng, tôi không biết rằng câu hỏi được gắn nhãn [C] chứ không phải [C ++]) và tôi đã sử dụng từ này "Nguyên thủy" như đối diện với "copmlex" (cấu trúc dữ liệu như một cấu trúc hoặc lớp).
mg30rg

3
Hãy để tôi thêm vào câu trả lời này rằng toán tử mảng []con cũng hủy bỏ một con trỏ ( a[b]được định nghĩa là trung bình *(a + b)).
cmaster - phục hồi monica

20

Một con trỏ là một "tham chiếu" đến một giá trị .. giống như số cuộc gọi của thư viện là một tham chiếu đến một cuốn sách. "Dereferences" số cuộc gọi đang thực sự đi qua và lấy cuốn sách đó.

int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;

// The * causes pA to DEREFERENCE...  `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4.. 

Nếu cuốn sách không có ở đó, người thủ thư bắt đầu la hét, tắt thư viện và một vài người được thiết lập để điều tra nguyên nhân một người sẽ tìm thấy một cuốn sách không có ở đó.


17

Nói một cách đơn giản, dereferencing phương tiện truy cập giá trị từ một vị trí bộ nhớ nhất định dựa vào đó con trỏ mà trỏ đến.


7

Mã và giải thích từ cơ bản về con trỏ :

Hoạt động hủy đăng ký bắt đầu tại con trỏ và theo mũi tên của nó để truy cập vào điểm của nó. Mục tiêu có thể là để xem xét trạng thái của con trỏ hoặc thay đổi trạng thái con trỏ. Hoạt động dereference trên một con trỏ chỉ hoạt động nếu con trỏ có một con trỏ - điểm được chỉ định và con trỏ phải được đặt để trỏ đến nó. Lỗi phổ biến nhất trong mã con trỏ là quên thiết lập điểm. Sự cố thời gian chạy phổ biến nhất do lỗi đó trong mã là một hoạt động hủy đăng ký không thành công. Trong Java, tính chính xác sẽ được hệ thống thời gian chạy gắn cờ một cách lịch sự. Trong các ngôn ngữ được biên dịch như C, C ++ và Pascal, đôi khi tính không chính xác sẽ bị sập và đôi khi bộ nhớ bị hỏng theo một cách ngẫu nhiên, tinh tế.

void main() {   
    int*    x;  // Allocate the pointer x
    x = malloc(sizeof(int));    // Allocate an int pointee,
                            // and set x to point to it
    *x = 42;    // Dereference x to store 42 in its pointee   
}

Bạn thực sự phải phân bổ bộ nhớ cho nơi x được cho là chỉ vào. Ví dụ của bạn có hành vi không xác định.
Peyman

3

Tôi nghĩ rằng tất cả các câu trả lời trước đó đều sai, vì họ tuyên bố rằng hội thảo có nghĩa là truy cập giá trị thực tế. Wikipedia đưa ra định nghĩa chính xác thay vào đó: https://en.wikipedia.org/wiki/Dereference_operator

Nó hoạt động trên một biến con trỏ và trả về giá trị l tương đương với giá trị tại địa chỉ con trỏ. Điều này được gọi là "hội thảo" con trỏ.

Điều đó nói rằng, chúng ta có thể hủy bỏ con trỏ mà không cần truy cập vào giá trị mà nó trỏ đến. Ví dụ:

char *p = NULL;
*p;

Chúng tôi đã hủy đăng ký con trỏ NULL mà không truy cập giá trị của nó. Hoặc chúng ta có thể làm:

p1 = &(*p);
sz = sizeof(*p);

Một lần nữa, hội nghị, nhưng không bao giờ truy cập giá trị. Mã như vậy sẽ KHÔNG gặp sự cố: Sự cố xảy ra khi bạn thực sự truy cập dữ liệu bằng một con trỏ không hợp lệ. Tuy nhiên, thật không may, theo tiêu chuẩn, việc hủy bỏ một con trỏ không hợp lệ là một hành vi không xác định (với một vài ngoại lệ), ngay cả khi bạn không thử chạm vào dữ liệu thực tế.

Vì vậy, trong ngắn hạn: hội nghị con trỏ có nghĩa là áp dụng toán tử dereference cho nó. Toán tử đó chỉ trả về giá trị l cho việc sử dụng trong tương lai của bạn.


tốt, bạn đã bỏ qua một con trỏ NULL, điều đó sẽ dẫn đến lỗi phân đoạn.
bò tót arjun

trên hết bạn đã tìm kiếm 'toán tử hội thảo' và không 'hội thảo một con trỏ', điều này thực sự có nghĩa là nhận giá trị / truy cập một giá trị tại một vị trí bộ nhớ được trỏ bởi một con trỏ.
bò tót arjun

Bạn đã thử chưa Tôi đã làm. Sau đây không sụp đổ: `#include <stdlib.h> int main () {char * p = NULL; * p; trả về 0; } `
stsp

1
@stsp Có phải vì mã không bị sập bây giờ không có nghĩa là nó sẽ không có trong tương lai hoặc trên một số hệ thống khác.

1
*p;gây ra hành vi không xác định. Mặc dù bạn đúng rằng hội nghị truyền hình không truy cập giá trị mỗi se , mã *p; sẽ truy cập giá trị.
MM
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.