Chính xác thì con trỏ C là gì nếu không phải là địa chỉ bộ nhớ?


206

Trong một nguồn có uy tín về C, thông tin sau được cung cấp sau khi thảo luận về &toán tử:

... Thật không may là thuật ngữ [địa chỉ] vẫn còn, bởi vì nó gây nhầm lẫn cho những người không biết địa chỉ là gì và đánh lừa những người làm: nghĩ về con trỏ như thể họ là địa chỉ thường dẫn đến đau buồn .. .

Các tài liệu khác mà tôi đã đọc (từ các nguồn có uy tín như nhau, tôi sẽ nói) luôn luôn đề cập đến các con trỏ và &toán tử như là đưa ra các địa chỉ bộ nhớ. Tôi rất thích tiếp tục tìm kiếm tính thực tế của vấn đề, nhưng thật khó khăn khi các nguồn có uy tín KIND OF không đồng ý.

Bây giờ tôi hơi bối rối - chính xác thì con trỏ là gì, nếu không phải là địa chỉ bộ nhớ?

PS

Sau đó, tác giả nói: ... Tôi sẽ tiếp tục sử dụng thuật ngữ 'địa chỉ của', bởi vì để phát minh ra một [thuật ngữ] khác sẽ còn tồi tệ hơn.


118
Một con trỏ là một biếngiữ một địa chỉ. Nó cũng có địa chỉ riêng của nó . Đây là sự khác biệt cơ bản giữa một con trỏ và một mảng. Một mảng có hiệu quả một địa chỉ (và theo ngụ ý, địa chỉ của nó là chính nó ).
WhozCraig

7
"Nguồn uy tín" của bạn cho báo giá là gì?
Bắp ngô

22
Nguồn uy tín cuối cùng là tiêu chuẩn ngôn ngữ và không phải là sách bán xuất phát từ nó và bán-từ-của-tác giả-mông. Tôi đã học được nó một cách khó khăn, hầu hết mọi lỗi lầm tôi có thể và từ từ xây dựng một mô hình tinh thần của C gần giống với mô tả của tiêu chuẩn và cuối cùng thay thế mô hình đã nói bằng mô hình của tiêu chuẩn.
Alexey Frunze

9
@thang Mọi người nghĩ con trỏ = số nguyên vì nó thường như vậy (x86 Linux và Windows "dạy" chúng tôi điều đó), bởi vì mọi người thích khái quát hóa, bởi vì mọi người không biết rõ về tiêu chuẩn ngôn ngữ và vì họ có ít kinh nghiệm với sự khác biệt hoàn toàn nền tảng. Những người đó có thể cho rằng một con trỏ tới dữ liệu và một con trỏ tới hàm có thể được chuyển đổi sang nhau và dữ liệu có thể được thực thi dưới dạng mã và mã được truy cập dưới dạng dữ liệu. Mặc dù điều này có thể đúng trên các kiến ​​trúc von Neuman (với 1 không gian địa chỉ), nhưng không nhất thiết đúng với các kiến ​​trúc Harvard (w / code & không gian dữ liệu).
Alexey Frunze

6
@exebook Tiêu chuẩn không dành cho người mới (đặc biệt là những người hoàn chỉnh). Họ không cần phải cung cấp những lời giới thiệu nhẹ nhàng và vô số ví dụ. Họ chính thức xác định một cái gì đó, vì vậy nó có thể được thực hiện chính xác bởi các chuyên gia.
Alexey Frunze

Câu trả lời:


148

Tiêu chuẩn C không xác định nội dung con trỏ là gì và cách thức hoạt động bên trong. Điều này là có chủ ý để không giới hạn số lượng nền tảng, trong đó C có thể được thực hiện như một ngôn ngữ được biên dịch hoặc giải thích.

Một giá trị con trỏ có thể là một số loại ID hoặc xử lý hoặc kết hợp một số ID (nói xin chào với các phân đoạn x86 và offset) và không nhất thiết phải là một địa chỉ bộ nhớ thực. ID này có thể là bất cứ thứ gì, thậm chí là một chuỗi văn bản có kích thước cố định. Các đại diện không có địa chỉ có thể đặc biệt hữu ích cho trình thông dịch C.


34
Không có nhiều điều để giải thích. Mỗi biến có địa chỉ của nó trong bộ nhớ. Nhưng bạn không phải lưu trữ địa chỉ của họ trong con trỏ cho họ. Thay vào đó, bạn có thể đánh số biến từ 1 đến bất cứ thứ gì và lưu số đó vào con trỏ. Điều đó là hoàn toàn hợp pháp theo tiêu chuẩn ngôn ngữ miễn là việc triển khai biết cách chuyển đổi các số đó thành địa chỉ và cách thực hiện số học con trỏ với các số đó và tất cả những thứ khác theo tiêu chuẩn.
Alexey Frunze

4
tôi muốn thêm rằng trên x86, một địa chỉ bộ nhớ bao gồm một bộ chọn phân đoạn và một phần bù, vì vậy đại diện cho một con trỏ là phân đoạn: offset vẫn đang sử dụng địa chỉ bộ nhớ.
thang

6
@Lundin Tôi không gặp vấn đề gì khi bỏ qua bản chất chung của tiêu chuẩn và không thể áp dụng khi tôi biết nền tảng và trình biên dịch của mình. Tuy nhiên, câu hỏi ban đầu là chung chung, vì vậy bạn không thể bỏ qua tiêu chuẩn khi trả lời nó.
Alexey Frunze

8
@Lundin Bạn không cần phải là nhà cách mạng hay nhà khoa học. Giả sử bạn muốn giả lập máy 32 bit trên máy 16 bit vật lý và bạn mở rộng 64KB RAM lên tới 4GB bằng cách sử dụng bộ lưu trữ đĩa và triển khai con trỏ 32 bit dưới dạng bù vào một tệp lớn. Những con trỏ không phải là địa chỉ bộ nhớ thực.
Alexey Frunze

6
Ví dụ điển hình nhất mà tôi từng thấy về điều này là việc triển khai C cho Symbolics Lisp Machines (khoảng năm 1990). Mỗi đối tượng C được triển khai như một mảng Lisp và các con trỏ được thực hiện như một cặp của một mảng và một chỉ mục. Do kiểm tra giới hạn mảng của Lisp, bạn không bao giờ có thể tràn từ đối tượng này sang đối tượng khác.
Barmar

62

Tôi không chắc chắn về nguồn của bạn, nhưng loại ngôn ngữ bạn mô tả đến từ tiêu chuẩn C:

6.5.3.2 Toán tử địa chỉ và toán tử gián tiếp
[...]
3. Toán tử unary & toán tử mang lại địa chỉ toán hạng của nó. [...]

Vì vậy, vâng, con trỏ trỏ đến địa chỉ bộ nhớ. Ít nhất đó là cách tiêu chuẩn C gợi ý nó.

Nói rõ hơn một chút, con trỏ là một biến chứa giá trị của một số địa chỉ . Địa chỉ của một đối tượng (có thể được lưu trữ trong một con trỏ) được trả về với toán tử đơn nguyên &.

Tôi có thể lưu địa chỉ "42 Wallaby Way, Sydney" trong một biến (và biến đó sẽ là "con trỏ" của các loại, nhưng vì đó không phải là địa chỉ bộ nhớ nên nó không phải là thứ mà chúng tôi gọi là "con trỏ"). Máy tính của bạn có địa chỉ cho các thùng bộ nhớ. Con trỏ lưu giá trị của một địa chỉ (tức là một con trỏ lưu giá trị "42 Wallaby Way, Sydney", là một địa chỉ).

Chỉnh sửa: Tôi muốn mở rộng về nhận xét của Alexey Frunze.

Chính xác thì con trỏ là gì? Hãy nhìn vào tiêu chuẩn C:

6.2.5 Các loại
[...]
20. [...]
Một loại con trỏ có thể được lấy từ một loại chức năng hoặc một loại đối tượng, được gọi là loại tham chiếu . Một loại con trỏ mô tả một đối tượng có giá trị cung cấp một tham chiếu đến một thực thể của loại được tham chiếu. Một loại con trỏ xuất phát từ loại T được tham chiếu đôi khi được gọi là '' con trỏ tới T ''. Việc xây dựng một loại con trỏ từ một loại tham chiếu được gọi là '' dẫn xuất loại con trỏ ''. Một loại con trỏ là một loại đối tượng hoàn chỉnh.

Về cơ bản, con trỏ lưu trữ một giá trị cung cấp một tham chiếu đến một số đối tượng hoặc hàm. Loại. Con trỏ được dự định để lưu trữ một giá trị cung cấp tham chiếu đến một số đối tượng hoặc hàm, nhưng không phải lúc nào cũng như vậy:

6.3.2.3 Con trỏ
[...]
5. Một số nguyên có thể được chuyển đổi thành bất kỳ loại con trỏ nào. Trừ khi được chỉ định trước đó, kết quả được xác định theo triển khai, có thể không được căn chỉnh chính xác, có thể không trỏ đến một thực thể của loại được tham chiếu và có thể là biểu diễn bẫy.

Trích dẫn ở trên nói rằng chúng ta có thể biến một số nguyên thành một con trỏ. Nếu chúng ta làm điều đó (nghĩa là, nếu chúng ta nhồi một giá trị nguyên vào một con trỏ thay vì một tham chiếu cụ thể đến một đối tượng hoặc hàm), thì con trỏ "có thể không trỏ đến một thực thể của kiểu tham chiếu" (nghĩa là nó có thể không cung cấp một thực thể tham chiếu đến một đối tượng hoặc chức năng). Nó có thể cung cấp cho chúng ta một cái gì đó khác. Và đây là một nơi mà bạn có thể gắn một loại tay cầm hoặc ID nào đó vào một con trỏ (tức là con trỏ không trỏ đến một đối tượng; nó lưu trữ một giá trị đại diện cho một cái gì đó, nhưng giá trị đó có thể không phải là một địa chỉ).

Vì vậy, có, như Alexey Frunze nói, có thể một con trỏ không lưu địa chỉ đến một đối tượng hoặc chức năng. Thay vào đó, một con trỏ có thể lưu trữ một số loại "xử lý" hoặc ID và bạn có thể làm điều này bằng cách gán một số giá trị nguyên tùy ý cho một con trỏ. Điều này xử lý hoặc ID đại diện phụ thuộc vào hệ thống / môi trường / bối cảnh. Miễn là hệ thống / triển khai của bạn có thể hiểu được giá trị, bạn đang ở trạng thái tốt (nhưng điều đó phụ thuộc vào giá trị cụ thể và hệ thống / triển khai cụ thể).

Thông thường , một con trỏ lưu một địa chỉ cho một đối tượng hoặc hàm. Nếu nó không lưu trữ một địa chỉ thực tế (cho một đối tượng hoặc chức năng), thì kết quả là xác định thực hiện (nghĩa là chính xác những gì xảy ra và con trỏ hiện đại diện phụ thuộc vào hệ thống và việc triển khai của bạn, vì vậy nó có thể là một điều khiển hoặc ID trên một hệ thống cụ thể, nhưng sử dụng cùng mã / giá trị trên một hệ thống khác có thể làm hỏng chương trình của bạn).

Điều đó đã kết thúc lâu hơn tôi nghĩ nó sẽ ...


3
Trong trình thông dịch C, một con trỏ có thể chứa ID / xử lý / địa chỉ không phải địa chỉ.
Alexey Frunze

4
@exebook Tiêu chuẩn không phải là giới hạn đối với biên dịch C.
Alexey Frunze

7
@Lundin Bravo! Hãy bỏ qua các tiêu chuẩn nhiều hơn! Như thể chúng ta chưa bỏ qua nó đủ và đã không sản xuất phần mềm lỗi và di động kém vì nó. Ngoài ra, xin vui lòng không rằng câu hỏi ban đầu là chung chung và vì vậy cần một câu trả lời chung chung.
Alexey Frunze

3
Khi những người khác nói rằng một con trỏ có thể là một tay cầm hoặc một cái gì đó không phải là một địa chỉ, họ không chỉ có nghĩa là bạn có thể ép dữ liệu vào một con trỏ bằng cách chuyển một số nguyên thành một con trỏ. Chúng có nghĩa là trình biên dịch có thể đang sử dụng một cái gì đó ngoài các địa chỉ bộ nhớ để thực hiện các con trỏ. Trên bộ xử lý Alpha có ABI của DEC, con trỏ hàm không phải là địa chỉ của hàm mà là địa chỉ của bộ mô tả hàm và bộ mô tả chứa địa chỉ của hàm và một số dữ liệu về các tham số hàm. Điểm đáng chú ý là tiêu chuẩn C rất linh hoạt.
Eric Postpischil

5
@Lundin: Khẳng định rằng các con trỏ được triển khai dưới dạng địa chỉ số nguyên trên 100% các hệ thống máy tính hiện có trong thế giới thực là sai. Máy tính tồn tại với địa chỉ từ và địa chỉ bù phân đoạn. Trình biên dịch vẫn tồn tại với sự hỗ trợ cho con trỏ gần và xa. Các máy tính PDP-11 tồn tại, với RSX-11 và Trình tạo tác vụ và các lớp phủ của nó, trong đó một con trỏ phải xác định thông tin cần thiết để tải một chức năng từ đĩa. Một con trỏ không thể có địa chỉ bộ nhớ của một đối tượng nếu đối tượng không có trong bộ nhớ!
Eric Postpischil

39

Con trỏ vs Biến

Trong bức ảnh này,

con trỏ_p là một con trỏ được đặt tại 0x12345 và đang trỏ đến một biến số biến ở 0x34567.


16
Điều này không chỉ không giải quyết khái niệm địa chỉ trái ngược với con trỏ, mà còn bỏ sót điểm mà một địa chỉ không chỉ là một số nguyên.
Gilles 'SO- đừng trở nên xấu xa'

19
-1, điều này chỉ giải thích con trỏ là gì. Đó không phải là câu hỏi-- và bạn đang gạt bỏ mọi phức tạp mà câu hỏi đang nói đến.
alexis

34

Nghĩ về một con trỏ như một địa chỉ là một xấp xỉ . Giống như tất cả các xấp xỉ, đôi khi nó đủ tốt để trở nên hữu ích, nhưng điều đó cũng không chính xác, điều đó có nghĩa là việc dựa vào nó gây ra rắc rối.

Một con trỏ giống như một địa chỉ trong đó nó chỉ ra nơi tìm một đối tượng. Một hạn chế ngay lập tức của sự tương tự này là không phải tất cả các con trỏ thực sự đều chứa một địa chỉ. NULLlà một con trỏ không phải là một địa chỉ. Thực tế, nội dung của một biến con trỏ có thể thuộc một trong ba loại:

  • các địa chỉ của một đối tượng, có thể được dereferenced (nếu pchứa địa chỉ của xthì biểu thức *pcó giá trị tương tự như x);
  • một con trỏ null , trong đó NULLlà một ví dụ;
  • Nội dung không hợp lệ , không trỏ đến một đối tượng (nếu pkhông giữ giá trị hợp lệ, thì *pcó thể làm bất cứ điều gì (không xác định được hành vi của LINE), với việc phá vỡ chương trình là một khả năng khá phổ biến).

Hơn nữa, sẽ chính xác hơn khi nói rằng một con trỏ (nếu hợp lệ và không null) chứa một địa chỉ: một con trỏ cho biết nơi tìm một đối tượng, nhưng có nhiều thông tin hơn gắn với nó.

Đặc biệt, một con trỏ có một loại. Trên hầu hết các nền tảng, loại con trỏ không có ảnh hưởng trong thời gian chạy, nhưng nó có ảnh hưởng vượt ra ngoài loại tại thời gian biên dịch. Nếu plà một con trỏ tới int( int *p;), sau đó p + 1trỏ đến một số nguyên là sizeof(int)byte sau p(giả sử p + 1vẫn là một con trỏ hợp lệ). Nếu qlà một con trỏ trỏ tới charcùng một địa chỉ với p( char *q = p;), thì đó q + 1không phải là cùng một địa chỉ với p + 1. Nếu bạn nghĩ con trỏ là địa chỉ, thì địa chỉ tiếp theo của Google là khác nhau đối với các con trỏ khác nhau cho cùng một vị trí.

Trong một số môi trường có thể có nhiều giá trị con trỏ với các biểu diễn khác nhau (các mẫu bit khác nhau trong bộ nhớ) trỏ đến cùng một vị trí trong bộ nhớ. Bạn có thể coi đây là những con trỏ khác nhau giữ cùng một địa chỉ hoặc là các địa chỉ khác nhau cho cùng một vị trí - ẩn dụ không rõ ràng trong trường hợp này. Các ==nhà điều hành luôn cho bạn biết liệu hai toán hạng được trỏ đến cùng một vị trí, vì vậy trên những môi trường bạn có thể có p == qpqcó mẫu bit khác nhau.

Thậm chí có những môi trường nơi con trỏ mang thông tin khác ngoài địa chỉ, chẳng hạn như thông tin về loại hoặc quyền. Bạn có thể dễ dàng trải qua cuộc sống của mình như một lập trình viên mà không gặp phải những điều này.

Có những môi trường mà các loại con trỏ khác nhau có biểu diễn khác nhau. Bạn có thể nghĩ về nó như các loại địa chỉ khác nhau có các đại diện khác nhau. Ví dụ, một số kiến ​​trúc có con trỏ byte và con trỏ từ, hoặc con trỏ đối tượng và con trỏ hàm.

Nói chung, nghĩ về con trỏ là địa chỉ không quá tệ miễn là bạn nhớ rằng

  • nó chỉ hợp lệ, con trỏ không null là địa chỉ;
  • bạn có thể có nhiều địa chỉ cho cùng một vị trí;
  • bạn không thể thực hiện số học trên các địa chỉ và không có thứ tự nào trên chúng;
  • con trỏ cũng mang thông tin loại.

Đi đường vòng khác là rắc rối hơn nhiều. Không phải mọi thứ trông giống như một địa chỉ đều có thể là một con trỏ . Ở đâu đó sâu bên dưới bất kỳ con trỏ nào được biểu diễn dưới dạng một mẫu bit có thể được đọc dưới dạng một số nguyên và bạn có thể nói rằng số nguyên này là một địa chỉ. Nhưng đi theo một cách khác, không phải mọi số nguyên là một con trỏ.

Có một số hạn chế nổi tiếng đầu tiên; ví dụ: một số nguyên chỉ định một vị trí bên ngoài không gian địa chỉ chương trình của bạn không thể là một con trỏ hợp lệ. Một địa chỉ bị sai lệch không tạo ra một con trỏ hợp lệ cho kiểu dữ liệu yêu cầu căn chỉnh; ví dụ, trên nền tảng intyêu cầu căn chỉnh 4 byte, 0x7654321 không thể là giá int*trị hợp lệ .

Tuy nhiên, nó vượt xa điều đó, bởi vì khi bạn biến một con trỏ thành một số nguyên, bạn sẽ gặp phải một thế giới rắc rối. Một phần lớn của vấn đề này là việc tối ưu hóa trình biên dịch tốt hơn nhiều so với việc lập trình vi mô hóa so với hầu hết các lập trình viên mong đợi, do đó mô hình tinh thần của họ về cách thức hoạt động của một chương trình là sai lầm sâu sắc. Chỉ vì bạn có con trỏ có cùng địa chỉ không có nghĩa là chúng tương đương nhau. Ví dụ: hãy xem xét đoạn trích sau:

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

Bạn có thể mong đợi rằng trên một cỗ máy hoạt động bình thường, sizeof(int)==4sizeof(short)==2điều này có thể in 1 = 1?(end-endian) hoặc 65536 = 1?(big-endian). Nhưng trên PC Linux 64 bit của tôi với GCC 4.4:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

GCC rất tốt bụng để cảnh báo chúng tôi những gì sai trong ví dụ đơn giản này - trong các ví dụ phức tạp hơn, trình biên dịch có thể không nhận thấy. Vì pcó một loại khác với &x, việc thay đổi pđiểm nào thành không thể ảnh hưởng đến &xđiểm nào (bên ngoài một số ngoại lệ được xác định rõ). Do đó, trình biên dịch có quyền tự do giữ giá trị của xmột thanh ghi và không cập nhật thanh ghi này khi *pthay đổi. Chương trình hủy bỏ hai con trỏ đến cùng một địa chỉ và thu được hai giá trị khác nhau!

Đạo đức của ví dụ này là suy nghĩ về một con trỏ (không hợp lệ) là một địa chỉ là tốt, miễn là bạn tuân thủ các quy tắc chính xác của ngôn ngữ C. Mặt trái của đồng xu là các quy tắc của ngôn ngữ C rất phức tạp và khó có được cảm giác trực quan trừ khi bạn biết điều gì xảy ra dưới mui xe. Và điều xảy ra dưới mui xe là sự ràng buộc giữa các con trỏ và địa chỉ có phần lỏng lẻo, cả hai đều hỗ trợ các kiến ​​trúc bộ xử lý của kỳ lạ.

Vì vậy, hãy nghĩ rằng con trỏ là địa chỉ là bước đầu tiên trong sự hiểu biết của bạn, nhưng đừng đi theo trực giác đó quá xa.


5
+1. Các câu trả lời khác dường như bỏ lỡ rằng một con trỏ đi kèm với thông tin loại. Điều này quan trọng hơn nhiều so với địa chỉ / ID / bất cứ cuộc thảo luận nào.
undur_gongor

+1 Điểm tuyệt vời về thông tin loại. Tôi không chắc chắn các ví dụ về trình biên dịch là chính xác ... Có vẻ như rất khó xảy ra, ví dụ, điều đó *p = 3được đảm bảo để thành công khi p chưa được khởi tạo.
LarsH

@LarsH Bạn nói đúng, cảm ơn, tôi đã viết nó như thế nào? Tôi đã thay thế nó bằng một ví dụ thậm chí thể hiện hành vi đáng ngạc nhiên trên PC của tôi.
Gilles 'SO- đừng trở nên xấu xa'

1
ừm, NULL là ((void *) 0) ..?
Aniket Inge

1
@ gnasher729 Con trỏ null một con trỏ. NULLkhông, nhưng đối với mức độ chi tiết cần thiết ở đây, đây là một sự xao lãng không liên quan. Ngay cả đối với lập trình hàng ngày, thực tế NULLcó thể được triển khai như một thứ không nói là con trỏ con trỏ không xuất hiện thường xuyên (chủ yếu chuyển NULLsang một hàm biến đổi - nhưng ngay cả ở đó, nếu bạn không truyền nó , bạn đã đưa ra giả định rằng tất cả các loại con trỏ có cùng biểu diễn).
Gilles 'SO- ngừng trở nên xấu xa'

19

Một con trỏ là một biến mà địa chỉ bộ nhớ HOLDS, không phải là địa chỉ chính nó. Tuy nhiên, bạn có thể hủy đăng ký một con trỏ - và có quyền truy cập vào vị trí bộ nhớ.

Ví dụ:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

Đó là nó. Nó đơn giản mà.

nhập mô tả hình ảnh ở đây

Một chương trình để chứng minh những gì tôi đang nói và đầu ra của nó là ở đây:

http://ideone.com/rcSUsb

Chương trình:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}

5
Nó có thể gây nhầm lẫn hơn nữa. Alice, bạn có thể thấy một con mèo? Không, tôi chỉ có thể thấy một nụ cười của một con mèo. Vì vậy, nói rằng con trỏ là một địa chỉ hoặc con trỏ là một biến chứa một địa chỉ hoặc nói rằng con trỏ là một tên của một khái niệm đề cập đến ý tưởng của một địa chỉ, những người viết sách có thể đi xa đến mức nào?
exebook

@exebook cho những người dày dạn trong con trỏ, nó khá đơn giản. Có lẽ một hình ảnh sẽ giúp?
Aniket Inge

5
Một con trỏ không nhất thiết phải giữ một địa chỉ. Trong một trình thông dịch C, nó có thể là một cái gì đó khác, một loại ID / xử lý.
Alexey Frunze

"Nhãn" hoặc tên biến là trình biên dịch / trình biên dịch và không tồn tại ở cấp độ máy nên tôi không nghĩ nó sẽ xuất hiện trong bộ nhớ.
Ben

1
@Aniket Một biến con trỏ có thể chứa giá trị con trỏ. Bạn chỉ cần lưu trữ kết quả của fopenmột biến nếu bạn cần sử dụng nó nhiều hơn một lần (mà, cho fopen, là khá nhiều thời gian).
Gilles 'SO- ngừng trở thành ác quỷ'

16

Thật khó để nói chính xác ý nghĩa của các tác giả của những cuốn sách đó. Con trỏ có chứa địa chỉ hay không phụ thuộc vào cách bạn xác định địa chỉ và cách bạn xác định con trỏ.

Đánh giá từ tất cả các câu trả lời được viết, một số người cho rằng (1) một địa chỉ phải là một số nguyên và (2) một con trỏ không cần phải ảo vì không được nói như vậy trong đặc tả. Với các giả định này, thì con trỏ rõ ràng không nhất thiết phải chứa địa chỉ.

Tuy nhiên, chúng tôi thấy rằng trong khi (2) có thể đúng, (1) có lẽ không phải là đúng. Và điều gì làm cho thực tế là & được gọi là địa chỉ của nhà điều hành theo câu trả lời của @ CornStalks? Điều này có nghĩa là các tác giả của đặc tả có ý định cho một con trỏ chứa địa chỉ?

Vì vậy, chúng ta có thể nói, con trỏ chứa một địa chỉ, nhưng một địa chỉ không phải là một số nguyên? Có lẽ.

Tôi nghĩ rằng tất cả những điều này là nói chuyện ngữ nghĩa khoa học nhảm nhí. Nó là hoàn toàn vô giá trị thực tế nói. Bạn có thể nghĩ về một trình biên dịch tạo mã theo cách mà giá trị của một con trỏ không phải là một địa chỉ không? Nếu vậy thì sao? Đó là những gì tôi nghĩ...

Tôi nghĩ những gì tác giả của cuốn sách (đoạn trích đầu tiên tuyên bố rằng con trỏ không nhất thiết chỉ là địa chỉ) có lẽ đang đề cập đến là một con trỏ đi kèm với nó là thông tin loại vốn có.

Ví dụ,

 int x;
 int* y = &x;
 char* z = &x;

cả y và z đều là con trỏ, nhưng y + 1 và z + 1 khác nhau. nếu chúng là địa chỉ bộ nhớ, các biểu thức đó có cung cấp cho bạn cùng một giá trị không?

Và ở đây, những suy nghĩ về con trỏ như thể chúng là địa chỉ thường dẫn đến đau buồn . Lỗi đã được viết bởi vì mọi người nghĩ về con trỏ như thể chúng là địa chỉ , và điều này thường dẫn đến đau buồn .

55555 có lẽ không phải là một con trỏ, mặc dù nó có thể là một địa chỉ, nhưng (int *) 55555 là một con trỏ. 55555 + 1 = 55556, nhưng (int *) 55555 + 1 là 55559 (+/- chênh lệch về kích thước (int)).


1
+1 để chỉ ra số học con trỏ không giống như số học trên các địa chỉ.
kutschkem

Trong trường hợp của 8086 16 bit, một địa chỉ bộ nhớ được mô tả bằng cơ sở phân đoạn + offset, cả 16 bit. Có nhiều kết hợp cơ sở phân đoạn + bù cho cùng một địa chỉ trong bộ nhớ. Con fartrỏ này không chỉ là "một số nguyên".
vonbrand

@vonbrand tôi không hiểu tại sao bạn đăng bình luận đó. vấn đề đó đã được thảo luận dưới dạng ý kiến ​​dưới các câu trả lời khác. chỉ cần mỗi câu trả lời khác giả định rằng địa chỉ = số nguyên và bất cứ điều gì không phải là số nguyên không phải là địa chỉ. tôi chỉ đơn giản chỉ ra điều này và lưu ý rằng nó có thể đúng hoặc không. toàn bộ quan điểm của tôi trong câu trả lời là nó không liên quan. tất cả chỉ là mô phạm, và vấn đề chính không được giải quyết trong các câu trả lời khác.
thang

@tang, ý tưởng "con trỏ == địa chỉ" là sai . Rằng tất cả mọi người và dì yêu thích của họ tiếp tục nói như vậy không làm cho nó đúng.
vonbrand

@vonbrand, và tại sao bạn lại bình luận dưới bài viết của tôi? Tôi không nói nó đúng hay sai. Trong thực tế, nó là đúng trong các kịch bản / giả định nhất định, nhưng không phải lúc nào cũng vậy. Hãy để tôi tóm tắt lại điểm của bài viết (lần thứ hai). toàn bộ quan điểm của tôi trong câu trả lời là nó không liên quan. tất cả chỉ là mô phạm, và vấn đề chính không được giải quyết trong các câu trả lời khác. sẽ thích hợp hơn để bình luận về các câu trả lời đưa ra yêu cầu rằng con trỏ == địa chỉ hoặc địa chỉ == số nguyên. xem ý kiến ​​của tôi dưới bài đăng của Alexey liên quan đến phân khúc: offset.
thang

15

Chà, một con trỏ là một sự trừu tượng đại diện cho một vị trí bộ nhớ. Lưu ý rằng trích dẫn không nói rằng suy nghĩ về con trỏ như thể chúng là địa chỉ bộ nhớ là sai, nó chỉ nói rằng nó "thường dẫn đến đau buồn". Nói cách khác, nó dẫn đến bạn có những kỳ vọng không chính xác.

Nguồn đau buồn có khả năng nhất chắc chắn là số học con trỏ, đây thực sự là một trong những thế mạnh của C. Nếu một con trỏ là một địa chỉ, bạn sẽ mong muốn số học của con trỏ là số học địa chỉ; Nhưng nó không phải là. Ví dụ: thêm 10 vào một địa chỉ sẽ cung cấp cho bạn một địa chỉ lớn hơn 10 đơn vị địa chỉ; nhưng việc thêm 10 vào một con trỏ sẽ tăng gấp 10 lần kích thước của loại đối tượng mà nó trỏ tới (và thậm chí không phải là kích thước thực tế, nhưng được làm tròn thành một ranh giới căn chỉnh). Với một int *kiến trúc thông thường với các số nguyên 32 bit, thêm 10 vào nó sẽ tăng thêm 40 đơn vị địa chỉ (byte). Các lập trình viên C có kinh nghiệm nhận thức được điều này và sống với nó, nhưng tác giả của bạn rõ ràng là không có fan hâm mộ của ẩn dụ cẩu thả.

Có thêm câu hỏi về cách nội dung của con trỏ biểu thị vị trí bộ nhớ: Như nhiều câu trả lời đã giải thích, một địa chỉ không phải luôn luôn là một int (hoặc dài). Trong một số kiến ​​trúc, một địa chỉ là một "phân khúc" cộng với phần bù. Một con trỏ thậm chí có thể chỉ chứa phần bù vào đoạn hiện tại (con trỏ "gần"), mà bản thân nó không phải là một địa chỉ bộ nhớ duy nhất. Và nội dung con trỏ có thể chỉ có mối quan hệ gián tiếp với địa chỉ bộ nhớ vì phần cứng hiểu nó. Nhưng tác giả của trích dẫn thậm chí không đề cập đến đại diện, vì vậy tôi nghĩ rằng đó là sự tương đương về mặt khái niệm, thay vì đại diện, mà họ có trong tâm trí.


12

Đây là cách tôi đã giải thích cho một số người bối rối trong quá khứ: Một con trỏ có hai thuộc tính ảnh hưởng đến hành vi của nó. Nó có một giá trị , đó là (trong các môi trường điển hình) một địa chỉ bộ nhớ và một loại , cho bạn biết loại và kích thước của đối tượng mà nó trỏ tới.

Ví dụ: đã cho:

union {
    int i;
    char c;
} u;

Bạn có thể có ba con trỏ khác nhau, tất cả đều trỏ đến cùng một đối tượng:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

Nếu bạn so sánh các giá trị của các con trỏ này, tất cả chúng đều bằng nhau:

v==i && i==c

Tuy nhiên, nếu bạn tăng từng con trỏ, bạn sẽ thấy loại mà chúng trỏ đến trở nên có liên quan.

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

Các biến icsẽ có các giá trị khác nhau tại thời điểm này, vì i++nguyên nhân ichứa địa chỉ của số nguyên có thể truy cập tiếp theo và c++gây ra ctrỏ đến ký tự có địa chỉ tiếp theo. Thông thường, số nguyên chiếm nhiều bộ nhớ hơn ký tự, do đó isẽ có giá trị lớn hơn csau khi cả hai được tăng lên.


2
+1 Cảm ơn bạn. Với con trỏ, giá trị và loại không thể tách rời vì người ta có thể tách cơ thể con người ra khỏi linh hồn của mình.
Aki Suihkonen

i == ckhông đúng định dạng (bạn chỉ có thể so sánh các con trỏ với các loại khác nhau nếu có một chuyển đổi ngầm định từ loại này sang loại khác). Hơn nữa, sửa lỗi này bằng cách truyền có nghĩa là bạn đã áp dụng một chuyển đổi, và sau đó việc tranh luận liệu chuyển đổi có thay đổi giá trị hay không. (Bạn có thể khẳng định rằng điều đó không xảy ra, nhưng sau đó, đó chỉ là khẳng định điều tương tự mà bạn đang cố chứng minh với ví dụ này).
MM

8

Mark Bessey đã nói rồi, nhưng điều này cần được nhấn mạnh lại cho đến khi hiểu.

Con trỏ có nhiều thứ để làm với một biến hơn là 3.

Con trỏ một tuple của một giá trị (của một địa chỉ) và một loại (với các thuộc tính bổ sung, chẳng hạn như chỉ đọc). Loại (và các tham số bổ sung nếu có) có thể xác định thêm hoặc hạn chế ngữ cảnh; ví dụ. __far ptr, __near ptr: bối cảnh của địa chỉ là gì: stack, heap, địa chỉ tuyến tính, offset từ đâu đó, bộ nhớ vật lý hoặc cái gì.

Đây là thuộc tính của kiểu làm cho số học con trỏ hơi khác với số học số nguyên.

Các ví dụ truy cập của một con trỏ không phải là một biến là quá nhiều để bỏ qua

  • fopen trả về một con trỏ TẬP_TIN. (đâu là biến)

  • con trỏ ngăn xếp hoặc con trỏ khung thường là các thanh ghi không thể nhấn

    *(int *)0x1231330 = 13; - truyền một giá trị số nguyên tùy ý sang loại con trỏ_of_integer và viết / đọc một số nguyên mà không bao giờ đưa ra một biến

Trong vòng đời của một chương trình C, sẽ có nhiều trường hợp con trỏ tạm thời khác không có địa chỉ - và do đó chúng không phải là biến, mà là biểu thức / giá trị với kiểu liên kết thời gian biên dịch.


8

Bạn đúng và lành mạnh. Thông thường, một con trỏ chỉ là một địa chỉ, vì vậy bạn có thể chuyển nó thành số nguyên và thực hiện bất kỳ số liệu nào.

Nhưng đôi khi con trỏ chỉ là một phần của một địa chỉ. Trên một số kiến ​​trúc, một con trỏ được chuyển đổi thành một địa chỉ có thêm cơ sở hoặc một thanh ghi CPU khác được sử dụng.

Nhưng ngày nay, trên kiến trúc PC và ARM với mô hình bộ nhớ phẳng và ngôn ngữ C được biên dịch nguyên bản, bạn có thể nghĩ rằng một con trỏ là một địa chỉ số nguyên ở một nơi nào đó trong RAM có thể định địa chỉ một chiều.


PC ... mô hình bộ nhớ phẳng? bộ chọn là gì?
thang

Rực rỡ. Và khi thay đổi kiến ​​trúc tiếp theo xuất hiện, có lẽ với các không gian dữ liệu quảng cáo mã riêng biệt hoặc ai đó quay lại kiến ​​trúc phân đoạn đáng kính (điều này có ý nghĩa bảo mật, thậm chí có thể thêm một số khóa vào số phân đoạn + bù để kiểm tra quyền) "con trỏ chỉ là số nguyên" đáng yêu đi xuống.
vonbrand

7

Một con trỏ, giống như bất kỳ biến nào khác trong C, về cơ bản là một tập hợp các bit có thể được biểu thị bằng một hoặc nhiều unsigned chargiá trị được nối (như với bất kỳ loại khả thi nào khác, sizeof(some_variable)sẽ chỉ ra số lượng unsigned chargiá trị). Điều làm cho một con trỏ khác với các biến khác là trình biên dịch C sẽ diễn giải các bit trong một con trỏ là xác định, bằng cách nào đó, một nơi mà một biến có thể được lưu trữ. Trong C, không giống như một số ngôn ngữ khác, có thể yêu cầu không gian cho nhiều biến và sau đó chuyển đổi một con trỏ thành bất kỳ giá trị nào trong tập hợp đó thành một con trỏ thành bất kỳ biến nào khác trong tập hợp đó.

Nhiều trình biên dịch triển khai các con trỏ bằng cách sử dụng các bit của chúng lưu trữ địa chỉ máy thực tế, nhưng đó không phải là triển khai duy nhất có thể. Việc triển khai có thể giữ một mảng - không thể truy cập được vào mã người dùng - liệt kê địa chỉ phần cứng và kích thước được phân bổ của tất cả các đối tượng bộ nhớ (bộ biến) mà chương trình đang sử dụng và mỗi con trỏ chứa một chỉ mục thành một mảng với một sự bù đắp từ chỉ số đó. Thiết kế như vậy sẽ cho phép một hệ thống không chỉ hạn chế mã chỉ hoạt động theo bộ nhớ mà nó sở hữu mà còn đảm bảo rằng con trỏ đến một mục bộ nhớ không thể vô tình được chuyển đổi thành một con trỏ sang một mục bộ nhớ khác (trong một hệ thống sử dụng phần cứng địa chỉ, nếu foobarlà mảng của 10 mục được lưu trữ liên tiếp trong bộ nhớ, một con trỏ tới mục "thứ mười một" củafoothay vào đó có thể trỏ đến mục đầu tiên của bar, nhưng trong một hệ thống mà mỗi "con trỏ" là ID đối tượng và phần bù, hệ thống có thể bẫy nếu mã cố gắng lập chỉ mục một con trỏ foovượt quá phạm vi được phân bổ của nó). Một hệ thống như vậy cũng có thể loại bỏ các vấn đề phân mảnh bộ nhớ, vì các địa chỉ vật lý liên quan đến bất kỳ con trỏ nào có thể được di chuyển xung quanh.

Lưu ý rằng mặc dù các con trỏ hơi trừu tượng, nhưng chúng không đủ trừu tượng để cho phép trình biên dịch C tuân thủ tiêu chuẩn đầy đủ thực hiện trình thu gom rác. Trình biên dịch C chỉ định rằng mọi biến, bao gồm các con trỏ, được biểu diễn dưới dạng một chuỗi các unsigned chargiá trị. Cho bất kỳ biến nào, người ta có thể phân tách nó thành một chuỗi các số và sau đó chuyển đổi chuỗi số đó trở lại thành một biến của loại ban đầu. Do đó, một chương trình có thểcallocmột số lưu trữ (nhận một con trỏ tới nó), lưu trữ một cái gì đó ở đó, phân tách con trỏ thành một chuỗi các byte, hiển thị chúng trên màn hình và sau đó xóa tất cả các tham chiếu đến chúng. Nếu sau đó chương trình chấp nhận một số số từ bàn phím, đã kết hợp lại các số đó với một con trỏ và sau đó cố đọc dữ liệu từ con trỏ đó và nếu người dùng nhập cùng số mà chương trình đã hiển thị trước đó, chương trình sẽ được yêu cầu xuất dữ liệu đã được lưu trữ trong callocbộ nhớ 'ed. Vì không có cách nào có thể hiểu được, máy tính có thể biết liệu người dùng có tạo ra một bản sao của các số được hiển thị hay không, liệu máy tính có thể biết được liệu bộ nhớ nói trên có thể được truy cập trong tương lai hay không.


Với chi phí rất lớn, có lẽ bạn có thể phát hiện bất kỳ việc sử dụng giá trị con trỏ nào có thể "rò rỉ" giá trị số của nó và ghim phân bổ để người thu gom rác sẽ không thu thập hoặc di chuyển nó (tất nhiên trừ khi freeđược gọi là rõ ràng). Việc triển khai kết quả có hữu ích hay không là một vấn đề khác, vì khả năng thu thập của nó có thể quá hạn chế, nhưng ít nhất bạn có thể gọi nó là trình thu gom rác :-) Việc gán con trỏ và số học sẽ không "rò rỉ" giá trị, nhưng mọi quyền truy cập vào một char*nguồn gốc không xác định sẽ phải được kiểm tra.
Steve Jessop

@SteveJessop: Tôi nghĩ rằng một thiết kế như vậy sẽ tồi tệ hơn vô dụng, vì mã sẽ không thể biết được con trỏ nào cần được giải phóng. Người thu gom rác giả định bất cứ thứ gì trông giống con trỏ là một người có thể quá bảo thủ, nhưng nhìn chung những thứ trông giống như - nhưng không - con trỏ có khả năng thay đổi, do đó tránh rò rỉ bộ nhớ "vĩnh viễn". Có bất kỳ hành động nào trông giống như nó đang phân tách một con trỏ thành byte đóng băng vĩnh viễn con trỏ là một công thức được đảm bảo cho rò rỉ bộ nhớ.
supercat

Tôi nghĩ dù sao nó cũng sẽ thất bại vì lý do hiệu năng - nếu bạn muốn mã của mình chạy chậm vì mọi quyền truy cập đều được kiểm tra thì đừng viết nó vào C ;-) Tôi có hy vọng cao hơn về sự khéo léo của lập trình viên C so với bạn, vì tôi nghĩ trong khi bất tiện, có lẽ không nên tránh việc phân bổ không cần thiết. Dù sao, C ++ định nghĩa chính xác "con trỏ dẫn xuất an toàn" để giải quyết vấn đề này, vì vậy chúng tôi biết phải làm gì nếu chúng tôi muốn tăng tính trừu tượng của con trỏ C đến mức chúng hỗ trợ thu gom rác hiệu quả.
Steve Jessop

@SteveJessop: Để hệ thống GC trở nên hữu ích, nó có thể giải phóng bộ nhớ một cách đáng tin cậy khi freechưa được gọi hoặc ngăn bất kỳ tham chiếu nào đến đối tượng được giải phóng khỏi tham chiếu đến đối tượng sống [ngay cả khi sử dụng tài nguyên yêu cầu quản lý trọn đời rõ ràng, GC vẫn có thể thực hiện hữu ích chức năng sau]; một hệ thống GC đôi khi coi sai các đối tượng là có tham chiếu trực tiếp đến chúng có thể sử dụng được nếu xác suất của các đối tượng N được ghim không cần thiết đồng thời tiến đến 0 khi N lớn . Trừ khi ai đó sẵn sàng gắn cờ lỗi biên dịch ...
supercat

... đối với mã là C ++ hợp lệ, nhưng trình biên dịch sẽ không thể chứng minh rằng một con trỏ không bao giờ có thể được chuyển đổi thành một dạng không thể nhận ra, tôi không thấy làm thế nào người ta có thể tránh được rủi ro mà một chương trình trong thực tế không bao giờ sử dụng con trỏ làm số nguyên có thể bị coi là sai khi làm như vậy.
30/1/2015

6

Một con trỏ là một loại biến có sẵn trong C / C ++ và chứa một địa chỉ bộ nhớ. Giống như bất kỳ biến nào khác, nó có một địa chỉ của riêng nó và chiếm bộ nhớ (số lượng là nền tảng cụ thể).

Một vấn đề bạn sẽ thấy do sự nhầm lẫn là cố gắng thay đổi người giới thiệu trong một hàm bằng cách chuyển con trỏ theo giá trị. Điều này sẽ tạo một bản sao của con trỏ ở phạm vi chức năng và mọi thay đổi về nơi con trỏ mới này "điểm" sẽ không thay đổi tham chiếu của con trỏ ở phạm vi đã gọi hàm. Để sửa đổi con trỏ thực trong hàm, người ta thường chuyển con trỏ sang con trỏ.


1
Nói chung, đó là một tay cầm / ID. Thông thường, đó là một địa chỉ đơn giản.
Alexey Frunze

Tôi đã điều chỉnh câu trả lời của mình để trở thành một PC nhiều hơn theo định nghĩa của Xử lý trong wikipedia. Tôi muốn đề cập đến các con trỏ như là một ví dụ cụ thể của một điều khiển, vì một điều khiển có thể chỉ đơn giản là một tham chiếu đến một con trỏ.
Matthew Sanders

6

TÓM TẮT TÓM TẮT (mà tôi cũng sẽ đặt lên hàng đầu):

(0) Nghĩ về con trỏ là địa chỉ thường là một công cụ học tập tốt và thường là triển khai thực tế cho con trỏ đến các loại dữ liệu thông thường.

(1) Nhưng trên nhiều, có lẽ hầu hết, các trình biên dịch con trỏ tới các hàm không phải là địa chỉ, nhưng lớn hơn một địa chỉ (thường là 2 lần, đôi khi nhiều hơn) hoặc thực sự là các con trỏ tới một cấu trúc trong bộ nhớ hơn là chứa các địa chỉ của hàm và các thứ như một hồ bơi không đổi.

(2) Con trỏ tới các thành viên dữ liệu và con trỏ tới các phương thức thường thậm chí còn xa lạ.

(3) Mã x86 kế thừa với các vấn đề về con trỏ FAR và NEAR

(4) Một số ví dụ, đáng chú ý nhất là IBM AS / 400, với "con trỏ chất béo" an toàn.

Tôi chắc chắn bạn có thể tìm thấy nhiều hơn.

CHI TIẾT:

UMMPPHHH !!!!! Nhiều câu trả lời cho đến nay là câu trả lời "lập trình viên" khá điển hình - nhưng không phải là trình biên dịch weenie hay weenie phần cứng. Vì tôi giả vờ là một weenie phần cứng và thường làm việc với các weenies trình biên dịch, hãy để tôi ném vào hai xu của mình:

Trên nhiều, có lẽ hầu hết, trình biên dịch C, một con trỏ tới dữ liệu thuộc loại T, trên thực tế, là địa chỉ của T.

Khỏe.

Nhưng, ngay cả trên nhiều trình biên dịch này, một số con trỏ nhất định KHÔNG phải là địa chỉ. Bạn có thể nói điều này bằng cách nhìn vào sizeof(ThePointer).

Ví dụ, con trỏ tới các chức năng đôi khi lớn hơn khá nhiều so với các địa chỉ thông thường. Hoặc, chúng có thể liên quan đến một mức độ gián tiếp. bài viết nàycung cấp một mô tả, liên quan đến bộ xử lý Intel Itanium, nhưng tôi đã thấy những người khác. Thông thường, để gọi một hàm, bạn phải biết không chỉ địa chỉ của mã chức năng, mà cả địa chỉ của nhóm hằng số của hàm - một vùng bộ nhớ mà các hằng số được tải với một lệnh tải, thay vì trình biên dịch phải tạo một hằng số 64 bit trong một số lệnh Tải tức thời và Shift và OR. Vì vậy, thay vì một địa chỉ 64 bit, bạn cần 2 địa chỉ 64 bit. Một số ABI (Giao diện nhị phân ứng dụng) di chuyển xung quanh thành 128 bit, trong khi một số khác sử dụng mức độ không xác định, với con trỏ hàm thực sự là địa chỉ của một mô tả chức năng chứa 2 địa chỉ thực tế vừa được đề cập. Cái nào tốt hơn? Phụ thuộc vào quan điểm của bạn: hiệu suất, kích thước mã, và một số vấn đề tương thích - thường mã giả định rằng một con trỏ có thể được chuyển thành dài hoặc dài, nhưng cũng có thể giả sử rằng độ dài dài chính xác là 64 bit. Mã này có thể không tuân thủ tiêu chuẩn, tuy nhiên khách hàng có thể muốn nó hoạt động.

Nhiều người trong chúng ta có những ký ức đau đớn về kiến ​​trúc phân đoạn Intel x86 cũ, với NEAR POINTERs và FAR POINTERS. Rất may, hiện tại chúng gần như đã tuyệt chủng, vì vậy chỉ có một bản tóm tắt nhanh: ở chế độ thực 16 bit, địa chỉ tuyến tính thực tế là

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

Trong khi ở chế độ được bảo vệ, nó có thể là

LinearAddress = SegmentRegister[SegNum].base + offset

với địa chỉ kết quả được kiểm tra theo giới hạn được đặt trong phân khúc. Một số chương trình được sử dụng không thực sự khai báo con trỏ C / C ++ FAR và NEAR tiêu chuẩn, nhưng nhiều người chỉ nói *T--- nhưng có trình chuyển đổi trình biên dịch và trình liên kết, ví dụ, con trỏ mã có thể ở gần con trỏ, chỉ là phần bù 32 bit so với bất cứ thứ gì trong thanh ghi CS (Phân đoạn mã), trong khi các con trỏ dữ liệu có thể là các con trỏ FAR, chỉ định cả số phân đoạn 16 bit và bù 32 bit cho giá trị 48 bit. Bây giờ, cả hai số lượng này chắc chắn có liên quan đến địa chỉ, nhưng vì chúng không cùng kích thước, nên số lượng nào là địa chỉ? Ngoài ra, các phân đoạn cũng mang các quyền - chỉ đọc, đọc-ghi, thực thi - ngoài các nội dung liên quan đến địa chỉ thực tế.

Một ví dụ thú vị hơn, IMHO, là (hoặc, có lẽ, là) họ AS / 400 của IBM. Máy tính này là một trong những máy đầu tiên triển khai HĐH trong C ++. Con trỏ trên machime này thường gấp 2 lần kích thước địa chỉ thực tế - ví dụ như bản trình bày nàycho biết, con trỏ 128 bit, nhưng địa chỉ thực tế là 48-64 bit, và một lần nữa, một số thông tin bổ sung, được gọi là khả năng, cung cấp các quyền như đọc, ghi, cũng như giới hạn để ngăn tràn bộ đệm. Có: bạn có thể thực hiện điều này một cách tương thích với C / C ++ - và nếu điều này có mặt ở khắp nơi, PLA và mafia Slav của Trung Quốc sẽ không xâm nhập vào nhiều hệ thống máy tính phương Tây. Nhưng trong lịch sử hầu hết các chương trình C / C ++ đã bỏ qua bảo mật cho hiệu năng. Thú vị nhất, gia đình AS400 cho phép hệ điều hành tạo ra các con trỏ an toàn, có thể được trao cho mã không có đặc quyền, nhưng mã không có đặc quyền không thể giả mạo hoặc giả mạo. Một lần nữa, bảo mật và trong khi mã C / C ++ tuân thủ tiêu chuẩn cẩu thả, nhiều tiêu chuẩn sẽ không hoạt động trong một hệ thống bảo mật như vậy. Một lần nữa, có các tiêu chuẩn chính thức,

Bây giờ, tôi sẽ thoát khỏi hộp xà phòng bảo mật của mình và đề cập đến một số cách khác mà con trỏ (thuộc nhiều loại khác nhau) thường không thực sự giải quyết: Con trỏ tới các thành viên dữ liệu, con trỏ tới các phương thức hàm thành viên và các phiên bản tĩnh của nó lớn hơn một địa chỉ thông thường. Như bài viết này nói:

Có nhiều cách để giải quyết vấn đề này [các vấn đề liên quan đến đơn lẻ so với nhiều người và thừa kế ảo]. Đây là cách trình biên dịch Visual Studio quyết định xử lý nó: Một con trỏ tới hàm thành viên của lớp được thừa kế thực sự là một cấu trúc. "Và họ tiếp tục nói" Đúc một con trỏ hàm có thể thay đổi kích thước của nó! ".

Như bạn có thể đoán từ sự bảo mật (trong) của tôi, tôi đã tham gia vào các dự án phần cứng / phần mềm C / C ++ trong đó một con trỏ được coi giống như một khả năng hơn là một địa chỉ thô.

Tôi có thể tiếp tục, nhưng tôi hy vọng bạn có được ý tưởng.

TÓM TẮT TÓM TẮT (mà tôi cũng sẽ đặt lên hàng đầu):

(0) nghĩ về con trỏ như địa chỉ thường là một công cụ học tập tốt và thường là triển khai thực tế cho con trỏ đến các loại dữ liệu thông thường.

(1) Nhưng trên nhiều, có lẽ hầu hết, các trình biên dịch con trỏ tới các hàm không phải là địa chỉ, nhưng lớn hơn một địa chỉ (thường là 2X, đôi khi nhiều hơn) hoặc thực sự là các con trỏ tới một cấu trúc trong bộ nhớ hơn là chứa các địa chỉ của hàm và các thứ như một hồ bơi không đổi.

(2) Con trỏ tới các thành viên dữ liệu và con trỏ tới các phương thức thường thậm chí còn xa lạ.

(3) Mã x86 kế thừa với các vấn đề về con trỏ FAR và NEAR

(4) Một số ví dụ, đáng chú ý nhất là IBM AS / 400, với "con trỏ chất béo" an toàn.

Tôi chắc chắn bạn có thể tìm thấy nhiều hơn.


Trong chế độ thực 16 bit LinearAddress = SegmentRegister.Selector * 16 + Offset(lưu ý lần 16, không thay đổi 16). Trong chế độ được bảo vệ LinearAddress = SegmentRegister.base + offset(không nhân bất kỳ loại nào; cơ sở phân đoạn được lưu trữ trong GDT / LDT và được lưu trong bộ đăng ký phân đoạn như hiện tại ).
Alexey Frunze

Bạn cũng đúng về cơ sở phân khúc. Tôi đã đánh giá sai. Đó là giới hạn phân đoạn được tùy chọn nhân với 4K. Cơ sở phân đoạn chỉ cần được xắp xếp lại bởi phần cứng khi nó tải một bộ mô tả phân đoạn từ bộ nhớ vào một thanh ghi phân đoạn.
Krazy Glew

4

Một con trỏ chỉ là một biến khác được sử dụng để giữ địa chỉ của một vị trí bộ nhớ (thường là địa chỉ bộ nhớ của một biến khác).


Vì vậy, các pointee thực sự là một địa chỉ bộ nhớ? Bạn không đồng ý với tác giả? Chỉ cần cố gắng để hiểu.
d0rmLife

Chức năng chính của con trỏ là trỏ đến một cái gì đó. Làm thế nào chính xác điều đó đạt được và liệu có một địa chỉ thực sự hay không, không được xác định. Một con trỏ có thể chỉ là một ID / xử lý, không phải là một địa chỉ thực.
Alexey Frunze

4

Bạn có thể thấy nó theo cách này. Một con trỏ là một giá trị đại diện cho một địa chỉ trong không gian bộ nhớ địa chỉ.


2
Một con trỏ không nhất thiết phải giữ địa chỉ bộ nhớ thực trong đó. Xem câu trả lời của tôi và (các) bình luận dưới nó.
Alexey Frunze

what .... con trỏ tới biến đầu tiên trên ngăn xếp không in 0. nó in đỉnh (hoặc dưới cùng) của khung stack tùy thuộc vào cách nó được thực hiện.
thang

@thang Đối với biến đầu tiên, đỉnh và đáy là như nhau. Và địa chỉ của đỉnh hoặc đáy trong trường hợp này là gì?
Valentin Radu

@ValentinRadu, tại sao bạn không thử nó .. rõ ràng là bạn chưa thử nó.
thang

2
@thang Bạn nói đúng, tôi đã làm một số giả định thực sự tồi tệ, để bảo vệ tôi là 5 giờ sáng ở đây.
Valentin Radu ngày

3

Một con trỏ chỉ là một biến khác có thể chứa địa chỉ bộ nhớ thường là của một biến khác. Một con trỏ là một biến nó cũng có một địa chỉ bộ nhớ.


1
Không nhất thiết phải là một địa chỉ. Btw, bạn đã đọc câu trả lời và ý kiến ​​hiện có trước khi đăng câu trả lời của bạn?
Alexey Frunze

3

Con trỏ AC rất giống với một địa chỉ bộ nhớ nhưng với các chi tiết phụ thuộc vào máy được trừu tượng hóa, cũng như một số tính năng không có trong tập lệnh cấp thấp hơn.

Ví dụ, một con trỏ C được gõ tương đối phong phú. Nếu bạn tăng một con trỏ qua một mảng các cấu trúc, nó sẽ nhảy độc đáo từ cấu trúc này sang cấu trúc khác.

Con trỏ phải tuân theo các quy tắc chuyển đổi và cung cấp kiểm tra loại thời gian biên dịch.

Có một giá trị "con trỏ null" đặc biệt có thể di động ở cấp mã nguồn, nhưng đại diện của chúng có thể khác nhau. Nếu bạn gán một hằng số nguyên có giá trị bằng 0 cho một con trỏ, thì con trỏ đó sẽ nhận giá trị con trỏ null. Ditto nếu bạn khởi tạo một con trỏ theo cách đó.

Một con trỏ có thể được sử dụng như một biến boolean: nó kiểm tra true nếu nó khác null và false nếu nó là null.

Trong ngôn ngữ máy, nếu con trỏ null là một địa chỉ vui như 0xFFFFFFFF, thì bạn có thể phải kiểm tra rõ ràng cho giá trị đó. C giấu điều đó từ bạn. Ngay cả khi con trỏ null là 0xFFFFFFFF, bạn có thể kiểm tra nó bằng cách sử dụngif (ptr != 0) { /* not null! */} .

Việc sử dụng các con trỏ lật đổ hệ thống loại dẫn đến hành vi không xác định, trong khi mã tương tự trong ngôn ngữ máy có thể được xác định rõ. Trình biên dịch sẽ tập hợp các hướng dẫn bạn đã viết, nhưng trình biên dịch C sẽ tối ưu hóa dựa trên giả định rằng bạn chưa làm gì sai. Nếu một float *pcon trỏ trỏ đến một long nbiến và *p = 0.0được thực thi, trình biên dịch không bắt buộc phải xử lý việc này. Việc sử dụng tiếp theo nsẽ không cần thiết đọc mẫu bit của giá trị float, nhưng có lẽ, đó sẽ là một truy cập được tối ưu hóa dựa trên giả định "răng cưa nghiêm ngặt" nchưa được chạm vào! Đó là, giả định rằng chương trình này hoạt động tốt, và vì vậy pkhông nên chỉ ra n.

Trong C, con trỏ tới mã và con trỏ tới dữ liệu là khác nhau, nhưng trên nhiều kiến ​​trúc, các địa chỉ là như nhau. Trình biên dịch C có thể được phát triển có con trỏ "béo", mặc dù kiến ​​trúc đích không có. Con trỏ chất béo có nghĩa là con trỏ không chỉ là địa chỉ máy, mà chứa thông tin khác, chẳng hạn như thông tin về kích thước của đối tượng được trỏ vào, để kiểm tra giới hạn. Các chương trình bằng văn bản sẽ dễ dàng chuyển đến các trình biên dịch như vậy.

Vì vậy, bạn có thể thấy, có nhiều sự khác biệt về ngữ nghĩa giữa địa chỉ máy và con trỏ C.


Con trỏ NULL không hoạt động theo cách bạn nghĩ họ làm trên tất cả các nền tảng - vui lòng xem câu trả lời của tôi cho CiscoIPPhone ở trên. NULL == 0 là một giả định chỉ giữ trên các nền tảng dựa trên x86. Công ước nói rằng các nền tảng mới phải phù hợp với x86, tuy nhiên, đặc biệt trong thế giới nhúng, điều này không phải như vậy. Chỉnh sửa: Ngoài ra, C không có gì để trừu tượng hóa giá trị của một con trỏ từ phần cứng - "ptr! = 0" sẽ không hoạt động như một thử nghiệm NULL trên nền tảng trong đó NULL! = 0.
DX-MON

1
DX-MON, điều đó hoàn toàn sai đối với tiêu chuẩn C. NULL được xác định là 0 và chúng có thể được sử dụng thay thế cho nhau trong các báo cáo. Cho dù trên không phải biểu diễn con trỏ NULL trong phần cứng là tất cả 0 bit đều không liên quan đến cách nó được biểu diễn trong mã nguồn.
Mark Bessey

@ DX-MON Tôi sợ bạn không làm việc với các sự kiện chính xác. Trong C, một biểu thức hằng tích phân đóng vai trò là hằng số con trỏ null, bất kể con trỏ null có phải là địa chỉ null hay không. Nếu bạn biết về trình biên dịch C ptr != 0không phải là kiểm tra null, vui lòng tiết lộ danh tính của nó (nhưng trước khi bạn làm điều đó, hãy gửi báo cáo lỗi cho nhà cung cấp).
Kaz

Tôi thấy những gì bạn đang nhận được, nhưng ý kiến ​​của bạn về con trỏ null không mạch lạc vì bạn nhầm lẫn con trỏ và địa chỉ bộ nhớ - chính xác là những gì trích dẫn trong câu hỏi khuyên bạn nên tránh! Phát biểu đúng: C định nghĩa con trỏ null là 0, bất kể địa chỉ bộ nhớ ở offset 0 có hợp pháp hay không.
alexis

1
@alexis Chương và câu thơ, xin vui lòng. C không định nghĩa con trỏ null là zero. C định nghĩa zero (hoặc bất kỳ biểu thức hằng số tích phân nào có giá trị bằng 0) như một cú pháp để biểu thị hằng số con trỏ null. faqs.org/faqs/C-faq/faq (phần 5).
Kaz

3

Trước khi hiểu con trỏ chúng ta cần hiểu đối tượng. Các đối tượng là các thực thể tồn tại và có một chỉ định vị trí được gọi là một địa chỉ. Một con trỏ chỉ là một biến giống như bất kỳ biến nào khác Ccó loại được gọi là pointernội dung được hiểu là địa chỉ của một đối tượng hỗ trợ thao tác sau.

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

Một con trỏ được phân loại dựa trên loại đối tượng mà nó hiện đang đề cập. Phần duy nhất của thông tin nó quan trọng là kích thước của đối tượng.

Bất kỳ đối tượng nào cũng hỗ trợ một hoạt động, &(địa chỉ của), truy xuất bộ xác định vị trí (địa chỉ) của đối tượng dưới dạng một loại đối tượng con trỏ. Điều này sẽ loại bỏ sự nhầm lẫn xung quanh danh pháp vì điều này sẽ có ý nghĩa để gọi &là một hoạt động của một đối tượng chứ không phải là một con trỏ có loại kết quả là một con trỏ của loại đối tượng.

Lưu ý Trong suốt phần giải thích này, tôi đã bỏ qua khái niệm về bộ nhớ.


Tôi thích lời giải thích của bạn về thực tế trừu tượng của một con trỏ chung trong một hệ thống chung. Nhưng, có lẽ thảo luận về bộ nhớ sẽ hữu ích. Thực tế, nói cho chính mình, tôi biết nó sẽ ...! Tôi nghĩ rằng thảo luận về kết nối có thể rất hữu ích để hiểu bức tranh lớn. Dù sao +1 :)
d0rmLife

@ d0rmLife: Bạn có đủ lời giải thích trong các câu trả lời khác bao gồm bức tranh lớn hơn. Tôi chỉ muốn đưa ra một lời giải thích trừu tượng toán học như một quan điểm khác. Ngoài ra IMHO, nó sẽ tạo ra ít nhầm lẫn hơn khi gọi &là 'Địa chỉ của` vì nó gắn chặt hơn với một Đối tượng hơn là con trỏ trên mỗi se`
Abhijit

Không xúc phạm, nhưng tôi sẽ tự quyết định giải thích đầy đủ là gì. Một cuốn sách giáo khoa không đủ để giải thích đầy đủ cấu trúc dữ liệu và phân bổ bộ nhớ. ;) .... dù sao đi nữa, câu trả lời của bạn vẫn hữu ích , ngay cả khi nó không phải là tiểu thuyết.
d0rmLife

Nó không có ý nghĩa để xử lý con trỏ mà không có khái niệm về bộ nhớ . Nếu đối tượng tồn tại mà không có bộ nhớ, nó phải ở một nơi, nơi không có địa chỉ - ví dụ như trong các thanh ghi. Để có thể sử dụng bộ nhớ giả định '&'.
Aki Suihkonen

3

Một địa chỉ được sử dụng để xác định một phần lưu trữ có kích thước cố định, thường là cho mỗi byte, dưới dạng một số nguyên. Đây được gọi chính xác là địa chỉ byte , cũng được sử dụng bởi ISO C. Có thể có một số phương thức khác để xây dựng một địa chỉ, ví dụ cho mỗi bit. Tuy nhiên, chỉ có địa chỉ byte thường được sử dụng, chúng ta thường bỏ qua "byte".

Về mặt kỹ thuật, một địa chỉ không bao giờ là một giá trị trong C, bởi vì định nghĩa của thuật ngữ "giá trị" trong (ISO) C là:

ý nghĩa chính xác của nội dung của một đối tượng khi được hiểu là có một loại cụ thể

(Nhấn mạnh bởi tôi.) Tuy nhiên, không có "loại địa chỉ" như vậy trong C.

Con trỏ không giống nhau. Con trỏ là một loại loại bằng ngôn ngữ C. Có một số loại con trỏ riêng biệt. Họ không nhất thiết phải tuân theo bộ quy tắc ngôn ngữ giống hệt nhau, ví dụ như ảnh hưởng của ++đến giá trị của loại int*so vớichar* .

Một giá trị trong C có thể là một loại con trỏ. Đây được gọi là một giá trị con trỏ . Để rõ ràng, một giá trị con trỏ không phải là một con trỏ trong ngôn ngữ C. Nhưng chúng ta đã quen với việc trộn chúng lại với nhau, bởi vì trong C không có nghĩa là mơ hồ: nếu chúng ta gọi một biểu thức plà "con trỏ", nó chỉ là một giá trị con trỏ nhưng không phải là một loại, vì một loại có tên trong C không phải là được biểu thị bằng một biểu thức , nhưng bằng tên loại hoặc tên typedef .

Một số điều khác là tinh tế. Là người dùng C, trước tiên, người ta nên biết ý objectnghĩa của nó:

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ị

Một đối tượng là một thực thể để biểu diễn các giá trị, thuộc loại cụ thể. Một con trỏ là một loại đối tượng . Vì vậy, nếu chúng ta khai báo int* p;, thì pcó nghĩa là "một đối tượng của loại con trỏ" hoặc "đối tượng con trỏ".

Lưu ý rằng không có "biến" được định nghĩa theo tiêu chuẩn (trong thực tế, nó không bao giờ được sử dụng làm danh từ của ISO C trong văn bản quy phạm). Tuy nhiên, không chính thức, chúng tôi gọi một đối tượng là một biến, như một số ngôn ngữ khác làm. (Nhưng vẫn không chính xác, ví dụ, trong C ++, một biến có thể là tham chiếu loại chuẩn, không phải là một đối tượng.) Các cụm từ "đối tượng con trỏ" hoặc "biến con trỏ" đôi khi được xử lý như "giá trị con trỏ" như trên, với một có thể có sự khác biệt nhỏ. (Một bộ ví dụ nữa là "mảng".)

Vì con trỏ là một loại và địa chỉ thực sự là "không chữ" trong C, nên một giá trị con trỏ đại khái "chứa" một địa chỉ. Và một biểu thức của loại con trỏ có thể mang lại một địa chỉ, ví dụ

ISO C11 6.5.2.3

3 &Toán tử đơn nguyên mang lại địa chỉ toán hạng của nó.

Lưu ý từ ngữ này được giới thiệu bởi WG14 / N1256, tức là ISO C99: TC3. Trong C99 có

3 &Toán tử đơn nguyên trả về địa chỉ toán hạng của nó.

Nó phản ánh ý kiến ​​của ủy ban: một địa chỉ không phải là giá trị con trỏ được trả về bởi toán tử đơn nguyên &.

Mặc dù các từ ngữ ở trên, vẫn có một số lộn xộn ngay cả trong các tiêu chuẩn.

ISO C11 6.6

9 Hằng số địa chỉ là một con trỏ rỗng, một con trỏ tới một giá trị chỉ định một đối tượng có thời lượng lưu trữ tĩnh hoặc một con trỏ tới một chỉ định hàm

ISO C ++ 11 5.19

3 ... Biểu thức hằng số địa chỉbiểu thức hằng số lõi prvalue của loại con trỏ đánh giá địa chỉ của đối tượng có thời lượng lưu trữ tĩnh, đến địa chỉ của hàm hoặc giá trị con trỏ null hoặc biểu thức hằng số lõi prvalue thuộc loại std::nullptr_t. ...

(Bản nháp tiêu chuẩn C ++ gần đây sử dụng từ ngữ khác nên không có vấn đề này.)

Trên thực tế cả "hằng số địa chỉ" trong C và "biểu thức hằng địa chỉ" trong C ++ đều là biểu thức không đổi của các loại con trỏ (hoặc ít nhất là các loại "giống như con trỏ" kể từ C ++ 11).

Và toán tử đơn nguyên dựng sẵn &được gọi là "địa chỉ của" trong C và C ++; tương tự, std::addressofđược giới thiệu trong C ++ 11.

Những đặt tên này có thể mang lại quan niệm sai lầm. Biểu thức kết quả là loại con trỏ, vì vậy chúng sẽ được hiểu là: kết quả chứa / mang lại một địa chỉ, thay vì một địa chỉ.


2

Nó nói "bởi vì nó gây nhầm lẫn cho những người không biết địa chỉ là gì" - cũng vậy, đó là sự thật: nếu bạn tìm hiểu địa chỉ là gì, bạn sẽ không nhầm lẫn. Về mặt lý thuyết, con trỏ là một biến trỏ đến một cái khác, thực tế giữ một địa chỉ, là địa chỉ của biến mà nó trỏ tới. Tôi không biết tại sao nên che giấu sự thật này, đó không phải là một khoa học tên lửa. Nếu bạn hiểu con trỏ, bạn sẽ tiến một bước để hiểu cách thức máy tính hoạt động. Hãy tiếp tục!


2

Nghĩ về nó, tôi nghĩ đó là vấn đề ngữ nghĩa. Tôi không nghĩ rằng tác giả đã đúng, vì tiêu chuẩn C đề cập đến một con trỏ là giữ một địa chỉ cho đối tượng được tham chiếu như những người khác đã đề cập ở đây. Tuy nhiên, địa chỉ! = Địa chỉ bộ nhớ. Một địa chỉ có thể thực sự là bất cứ thứ gì theo tiêu chuẩn C mặc dù cuối cùng nó sẽ dẫn đến một địa chỉ bộ nhớ, chính con trỏ có thể là id, offset + selector (x86), thực sự là bất cứ thứ gì miễn là nó có thể mô tả (sau khi ánh xạ) bất kỳ bộ nhớ nào địa chỉ trong không gian địa chỉ.


Một con trỏ giữ một địa chỉ (hoặc không, nếu nó không). Nhưng đó một địa chỉ khác xa với nó một địa chỉ: ví dụ, hai con trỏ đến cùng một địa chỉ nhưng với một loại khác nhau không tương đương trong nhiều tình huống.
Gilles 'SO- ngừng trở nên xấu xa'

@Gilles Nếu bạn thấy "hiện hữu", như trong int i=5-> i 5 thì con trỏ là địa chỉ có. Ngoài ra, null có một địa chỉ là tốt. Thường là một địa chỉ ghi không hợp lệ (nhưng không nhất thiết, xem chế độ thực x86), nhưng địa chỉ không hơn không kém. Thực sự chỉ có 2 yêu cầu cho null: đảm bảo so sánh không bằng với một con trỏ với một đối tượng thực tế và bất kỳ hai con trỏ null sẽ so sánh bằng nhau.
Valentin Radu

Ngược lại, một con trỏ null được đảm bảo không bằng địa chỉ của bất kỳ đối tượng nào. Dereferences một con trỏ null là hành vi không xác định. Một vấn đề lớn khi nói rằng, con trỏ là địa chỉ, đó là chúng hoạt động khác nhau. Nếu plà một con trỏ, p+1không phải lúc nào địa chỉ cũng tăng thêm 1.
Gilles 'SO- ngừng trở thành ác quỷ'

Xin vui lòng đọc lại bình luận it's guaranteed to compare unequal to a pointer to an actual object. Đối với các mỹ phẩm con trỏ, tôi không thấy điểm, giá trị của con trỏ vẫn là một địa chỉ, ngay cả khi thao tác "+" sẽ không nhất thiết phải thêm một byte vào nó.
Valentin Radu ngày

1

Một cách khác mà con trỏ C hoặc C ++ khác với địa chỉ bộ nhớ đơn giản do các loại con trỏ khác nhau mà tôi chưa thấy trong các câu trả lời khác (mặc dù có tổng kích thước của chúng, tôi có thể đã bỏ qua nó). Nhưng nó có lẽ là một trong những quan trọng nhất, bởi vì ngay cả các lập trình viên C / C ++ có kinh nghiệm cũng có thể vượt qua nó:

Trình biên dịch có thể giả định rằng các con trỏ của các loại không tương thích không trỏ đến cùng một địa chỉ ngay cả khi chúng rõ ràng, điều này có thể đưa ra hành vi không thể thực hiện được với một mô hình địa chỉ con trỏ đơn giản ==. Hãy xem xét các mã sau (giả sử sizeof(int) = 2*sizeof(short)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

Lưu ý rằng có một ngoại lệ char*, vì vậy thao tác sử dụng các giá trị char*là có thể (mặc dù không dễ mang theo).


0

Tóm tắt nhanh: Địa chỉ AC là một giá trị, thường được biểu thị dưới dạng địa chỉ bộ nhớ cấp máy, với một loại cụ thể.

Từ "con trỏ" không đủ tiêu chuẩn là mơ hồ. C có các đối tượng con trỏ (biến), loại con trỏ , biểu thức con trỏ và giá trị con trỏ .

Việc sử dụng từ "con trỏ" rất phổ biến có nghĩa là "đối tượng con trỏ" và điều đó có thể dẫn đến một số nhầm lẫn - đó là lý do tại sao tôi cố gắng sử dụng "con trỏ" như một tính từ hơn là một danh từ.

Tiêu chuẩn C, ít nhất là trong một số trường hợp, sử dụng từ "con trỏ" có nghĩa là "giá trị con trỏ". Ví dụ, mô tả của malloc nói rằng nó "trả về một con trỏ null hoặc một con trỏ tới không gian được phân bổ".

Vậy địa chỉ trong C là gì? Đó là một giá trị con trỏ, tức là giá trị của một số loại con trỏ cụ thể. (Ngoại trừ việc giá trị con trỏ null không nhất thiết được gọi là "địa chỉ", vì đó không phải là địa chỉ của bất cứ thứ gì).

Mô tả tiêu chuẩn của toán tử đơn nguyên &cho biết nó "mang lại địa chỉ toán hạng của nó". Ngoài tiêu chuẩn C, từ "địa chỉ" thường được sử dụng để chỉ địa chỉ bộ nhớ (vật lý hoặc ảo), thường là một từ có kích thước (bất kể "từ" nào trên một hệ thống nhất định).

"Địa chỉ" AC thường được triển khai dưới dạng địa chỉ máy - giống như intgiá trị C thường được triển khai dưới dạng từ máy. Nhưng một địa chỉ C (giá trị con trỏ) không chỉ là một địa chỉ máy. Đó là một giá trị thường được biểu thị dưới dạng địa chỉ máy và đó là một giá trị với một số loại cụ thể .


0

Một giá trị con trỏ một địa chỉ. Một biến con trỏ một đối tượng có thể lưu trữ một địa chỉ. Điều này đúng bởi vì đó là những gì tiêu chuẩn xác định một con trỏ là. Điều quan trọng là phải nói điều đó với người mới C vì người mới C thường không rõ ràng về sự khác biệt giữa một con trỏ và điều mà nó chỉ ra (nghĩa là, họ không biết sự khác biệt giữa phong bì và tòa nhà). Khái niệm địa chỉ (mọi đối tượng đều có địa chỉ và đó là những gì một con trỏ lưu trữ) rất quan trọng vì nó sắp xếp nó ra.

Tuy nhiên, các cuộc đàm phán tiêu chuẩn ở một mức độ trừu tượng cụ thể. Những người mà tác giả nói về những người "biết địa chỉ là gì", nhưng những người mới biết về C, nhất thiết phải học về các địa chỉ ở một mức độ trừu tượng khác nhau - có lẽ bằng ngôn ngữ lắp ráp lập trình. Không có gì đảm bảo rằng việc triển khai C sử dụng cùng một đại diện cho các địa chỉ như các mã opc CPU sử dụng (gọi là "địa chỉ cửa hàng" trong đoạn này), mà những người này đã biết.

Ông tiếp tục nói về "thao tác địa chỉ hoàn toàn hợp lý". Theo như tiêu chuẩn C về cơ bản thì không có thứ gọi là "thao tác địa chỉ hoàn toàn hợp lý". Bổ sung được xác định trên con trỏ và về cơ bản là nó. Chắc chắn, bạn có thể chuyển đổi một con trỏ thành số nguyên, thực hiện một số op bit bit hoặc số học, và sau đó chuyển đổi nó trở lại. Điều này không được đảm bảo để hoạt động theo tiêu chuẩn, vì vậy trước khi viết mã đó, bạn nên biết rõ hơn cách triển khai C cụ thể của bạn đại diện cho con trỏ và thực hiện chuyển đổi đó. Nó có thể sử dụng đại diện địa chỉ mà bạn mong đợi, nhưng đó không phải là lỗi của bạn vì bạn đã không đọc hướng dẫn. Đó không phải là nhầm lẫn, đó là quy trình lập trình không chính xác ;-)

Nói tóm lại, C sử dụng khái niệm trừu tượng hơn về một địa chỉ so với tác giả.

Tất nhiên, khái niệm về địa chỉ của tác giả cũng không phải là từ cấp thấp nhất về vấn đề này. Điều gì với bản đồ bộ nhớ ảo và địa chỉ RAM vật lý trên nhiều chip, số mà bạn nói với CPU là "địa chỉ cửa hàng" mà bạn muốn truy cập về cơ bản không liên quan gì đến việc dữ liệu bạn muốn thực sự nằm ở đâu trong phần cứng. Đó là tất cả các lớp của sự gián tiếp và đại diện, nhưng tác giả đã chọn một lớp để đặc quyền. Nếu bạn sẽ làm điều đó khi nói về C, hãy chọn cấp độ C để đặc quyền !

Cá nhân tôi không nghĩ rằng những nhận xét của tác giả là hữu ích, ngoại trừ trong bối cảnh giới thiệu C cho các lập trình viên lắp ráp. Chắc chắn không hữu ích cho những người đến từ các ngôn ngữ cấp cao hơn để nói rằng các giá trị con trỏ không có địa chỉ. Sẽ tốt hơn nhiều khi thừa nhận sự phức tạp hơn là nói rằng CPU có độc quyền trong việc nói địa chỉ là gì và do đó các giá trị con trỏ C "không phải là" địa chỉ. Chúng là địa chỉ, nhưng chúng có thể được viết bằng một ngôn ngữ khác với địa chỉ mà anh ấy muốn nói. Phân biệt hai điều trong ngữ cảnh của C là "địa chỉ" và "địa chỉ cửa hàng" sẽ là đủ, tôi nghĩ vậy.


0

Nói một cách đơn giản, con trỏ thực sự là một phần của cơ chế phân đoạn, dịch sang Địa chỉ tuyến tính sau khi phân đoạn và sau đó đến địa chỉ Vật lý sau khi phân trang. Địa chỉ vật lý thực sự được giải quyết từ bạn ram.

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
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.