Vui lòng bao gồm một ví dụ với lời giải thích.
int *p;
sẽ định nghĩa một con trỏ tới một số nguyên và *p
sẽ bỏ qua con trỏ đó, nghĩa là nó thực sự sẽ lấy dữ liệu mà p trỏ tới.
Vui lòng bao gồm một ví dụ với lời giải thích.
int *p;
sẽ định nghĩa một con trỏ tới một số nguyên và *p
sẽ bỏ qua con trỏ đó, nghĩa là nó thực sự sẽ lấy dữ liệu mà p trỏ tới.
Câu trả lời:
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.
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 ++.
Hãy xem xét trong C, đưa ra một con trỏ như p
dướ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à p
con 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
.
Để đề cập đến các ký tự được p
trỏ đến, chúng tôi p
sử 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 x
và 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
.
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_
Để 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]
Đô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 new
toá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.
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);
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_ptr
khi 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
Trong C, NULL
và 0
- 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 bools
phả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à static
biế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();
và 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
, NULL
và nullptr
để 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
, nullptr
hoặ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 ++ ...
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 NULL
hoặ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 int
biế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ó int
biế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]
và *(p + 1)
giống hệt nhau ? Đó là, Có p[1]
và *(p + 1)
tạo ra các hướng dẫn tương tự?
p
chỉ là 2000: nếu bạn có một con trỏ khác p
thì 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.
u
có 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.
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.
[]
con cũng hủy bỏ một con trỏ ( a[b]
được định nghĩa là trung bình *(a + b)
).
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ó ở đó.
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.
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
}
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.
*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ị.