2012-02-18 25 views
12

Tôi đã tự hỏi, cách tham chiếu được lưu trữ nội bộ? Tôi cảm thấy rằng một sự hiểu biết sâu bên dưới mức đó sẽ làm cho tôi hiểu con trỏ khái niệm so với tham chiếu tốt hơn và đưa ra quyết định lựa chọn.Tham chiếu được lưu trữ bên trong bằng C++ như thế nào?

Tôi nghi ngờ về cơ bản nó hoạt động giống như con trỏ nhưng trình biên dịch sẽ xử lý các con trỏ xử lý. Xin cho biết.

+6

"Tôi cảm thấy rằng sự hiểu biết sâu bên dưới mức đó sẽ làm cho tôi hiểu con trỏ khái niệm so với tham chiếu tốt hơn" Tôi không nghĩ điều đó sẽ giúp ích gì. –

+1

Bạn có thể thử điều này: http://eetimes.com/discussion/programming-pointers/4023307/References-vs-Pointers – Vlad

+0

@ MR.Anubis ..... bạn có ý gì? – howtechstuffworks

Trả lời

11

Tham chiếu chỉ là bí danh bên trong trình biên dịch xử lý chúng giống như con trỏ.

Nhưng đối với người dùng từ góc độ sử dụng, có một số khác biệt tinh tế.

Một số khác biệt chính là:

  • Pointers có thể NULL trong khi tài liệu tham khảo cannot.There là không có gì gọi là NULL tham khảo.
  • Tham chiếu const kéo dài tuổi thọ của một giới hạn tạm thời cho nó. Không có tương đương với con trỏ.

Bên cạnh đó, tài liệu tham khảo có một số điểm chung với const con trỏ (không phải là một con trỏ đến const):

  • Tài liệu tham khảo phải được khởi tạo tại thời điểm tạo ra.
  • Tham chiếu bị ràng buộc vĩnh viễn với một vị trí lưu trữ duy nhất và không thể khôi phục sau này.

Khi bạn biết bạn có thứ gì đó (một đối tượng) để tham khảo và bạn sẽ không bao giờ muốn tham chiếu đến bất kỳ điều gì khác sử dụng tham chiếu con trỏ sử dụng khác.

+0

Nó không hoàn toàn giống như con trỏ (không phải lúc nào). Trình biên dịch được phép tách bạch các tham chiếu hoàn toàn, ví dụ trong 'lớp A {public: A(): a (_a) {} int const & a; riêng: int _a; }; ', tham chiếu có thể được loại bỏ (và do đó có thể không ảnh hưởng đến kích thước của đối tượng) trong khi một con trỏ * không thể * được elided. –

+0

"trong khi tham chiếu không thể" 'int & a = * ((int *) 0);' – SigTerm

+5

@SigTerm Nếu bạn nhắm cẩn thận, bạn có thể tự bắn mình vào chân. –

14

Không yêu cầu tham chiếu phải được "lưu trữ" theo bất kỳ cách nào. Theo như ngôn ngữ có liên quan, một tham chiếu chỉ là một bí danh của một số đối tượng hiện có, và đó là tất cả những gì mà trình biên dịch phải cung cấp. Có thể hoàn toàn có thể là không cần lưu trữ bất cứ thứ gì nếu tham chiếu chỉ là một tay ngắn cho một số đối tượng khác đã có trong phạm vi, hoặc nếu một hàm có đối số tham chiếu được gạch chân.

Trong tình huống mà các tài liệu tham khảo cần được tỏ ra (ví dụ khi gọi một hàm trong một đơn vị dịch thuật khác nhau), bạn thực tế có thể thực hiện một T & x như một T * const và điều trị mỗi lần xuất hiện của x như ngầm dereferencing con trỏ đó. Thậm chí ở cấp độ cao hơn, bạn có thể nghĩ đến số T & x = y;T * const p = &y; (và tương ứng là x*p) về cơ bản tương đương, vì vậy đây sẽ là cách rõ ràng để triển khai tham chiếu.

Nhưng tất nhiên không có yêu cầu và mọi triển khai đều miễn phí để làm bất cứ điều gì nó muốn.

+0

Tôi thấy, từ bạn đăng bài, tôi nghĩ rằng bạn đang nói, rằng để sử dụng tài liệu tham khảo, không cần phải phân bổ bất kỳ biến thêm, u chỉ có thể thêm tên vào bảng các biến bạn đang duy trì (mặc dù giả định của tôi). Tôi không biết, C++ hoạt động như thế nào, nhưng nếu tôi thực hiện một ngôn ngữ oop mới, tôi vẫn có thể đảm bảo rằng không có biến phụ nào được cấp phát trong bộ nhớ, chỉ cần nhớ biến 'a' có tên khác là "a_ref" ??? ? Tôi đã làm đúng chứ? – howtechstuffworks

+1

@howtechstuffworks: Miễn là tham chiếu chỉ đề cập đến biến phạm vi địa phương hoặc thành viên, thì có. – Puppy

2

Xin lỗi vì đã sử dụng assembly để giải thích điều này nhưng tôi nghĩ đây là cách tốt nhất để hiểu cách tham chiếu được thực hiện bởi trình biên dịch.

#include <iostream> 

using namespace std; 

int main() 
{ 
    int i = 10; 
    int *ptrToI = &i; 
    int &refToI = i; 

    cout << "i = " << i << "\n"; 
    cout << "&i = " << &i << "\n"; 

    cout << "ptrToI = " << ptrToI << "\n"; 
    cout << "*ptrToI = " << *ptrToI << "\n"; 
    cout << "&ptrToI = " << &ptrToI << "\n"; 

    cout << "refToNum = " << refToI << "\n"; 
    //cout << "*refToNum = " << *refToI << "\n"; 
    cout << "&refToNum = " << &refToI << "\n"; 

    return 0; 
} 

Output của mã này là như thế này

i = 10 
&i = 0xbf9e52f8 
ptrToI = 0xbf9e52f8 
*ptrToI = 10 
&ptrToI = 0xbf9e52f4 
refToNum = 10 
&refToNum = 0xbf9e52f8 

Cho phép xem xét tháo gỡ (tôi đã sử dụng GDB cho việc này. 8,9 và 10 ở đây là những con số dòng mã)

8   int i = 10; 
0x08048698 <main()+18>: movl $0xa,-0x10(%ebp) 

Ở đây $0xa là 10 (số thập phân) mà chúng tôi đang gán cho i. -0x10(%ebp) ở đây có nghĩa là nội dung của ebp register –16 (thập phân). -0x10(%ebp) trỏ đến địa chỉ i trên ngăn xếp.

9   int *ptrToI = &i; 
0x0804869f <main()+25>: lea -0x10(%ebp),%eax 
0x080486a2 <main()+28>: mov %eax,-0x14(%ebp) 

Chỉ định địa chỉ i to ptrToI. ptrToI một lần nữa trên ngăn xếp nằm tại địa chỉ -0x14(%ebp), đó là ebp - 20 (thập phân).

10   int &refToI = i; 
0x080486a5 <main()+31>: lea -0x10(%ebp),%eax 
0x080486a8 <main()+34>: mov %eax,-0xc(%ebp) 

Bây giờ ở đây là bắt! So sánh việc tháo gỡ dòng 9 và 10 và bạn sẽ quan sát rằng, -0x14(%ebp) được thay thế bằng -0xc(%ebp) trong dòng số 10. -0xc(%ebp) là địa chỉ của refToNum. Nó được cấp phát trên stack. Nhưng bạn sẽ không bao giờ có thể lấy địa chỉ này từ mã của bạn vì bạn không bắt buộc phải biết địa chỉ.

Vì vậy; một tham chiếu không chiếm bộ nhớ. Trong trường hợp này, nó là bộ nhớ ngăn xếp vì chúng ta đã phân bổ nó như là một biến cục bộ. Bộ nhớ chiếm bao nhiêu bộ nhớ? Con trỏ chiếm nhiều.

Bây giờ, hãy xem cách chúng tôi truy cập tham chiếu và con trỏ. Để đơn giản, tôi đã chỉ hiển thị một phần của đoạn mã lắp ráp

16   cout << "*ptrToI = " << *ptrToI << "\n"; 
0x08048746 <main()+192>:  mov -0x14(%ebp),%eax 
0x08048749 <main()+195>:  mov (%eax),%ebx 
19   cout << "refToNum = " << refToI << "\n"; 
0x080487b0 <main()+298>:  mov -0xc(%ebp),%eax 
0x080487b3 <main()+301>:  mov (%eax),%ebx 

Bây giờ, so sánh hai dòng trên, bạn sẽ thấy sự giống nhau nổi bật. -0xc(%ebp) là địa chỉ thực tế của refToI mà bạn không bao giờ có thể truy cập được. Nói một cách đơn giản, nếu bạn nghĩ tham chiếu như một con trỏ bình thường, thì việc truy cập một tham chiếu giống như tìm nạp giá trị tại địa chỉ được trỏ tới bởi tham chiếu. Có nghĩa là hai dòng dưới đây của mã sẽ cung cấp cho bạn kết quả tương tự

cout << "Value if i = " << *ptrToI << "\n"; 
cout << " Value if i = " << refToI << "\n"; 

Bây giờ so sánh này

15   cout << "ptrToI = " << ptrToI << "\n"; 
0x08048713 <main()+141>:  mov -0x14(%ebp),%ebx 
21   cout << "&refToNum = " << &refToI << "\n"; 
0x080487fb <main()+373>:  mov -0xc(%ebp),%eax 

Tôi đoán bạn có thể nhận ra những gì đang xảy ra ở đây. Nếu bạn yêu cầu &refToI, nội dung của -0xc(%ebp) vị trí địa chỉ được trả lại và -0xc(%ebp) là nơi refToi cư trú và nội dung của nó không là gì ngoài địa chỉ của i.

Điều cuối cùng, Tại sao dòng này lại nhận xét?

//cout << "*refToNum = " << *refToI << "\n"; 

Vì không được phép *refToI và nó sẽ cho bạn lỗi thời gian biên dịch.

Các vấn đề liên quan