Không phải việc sử dụng các biến con trỏ là chi phí bộ nhớ sao?


29

Trong các ngôn ngữ như C và C ++, trong khi sử dụng con trỏ tới các biến, chúng ta cần thêm một vị trí bộ nhớ để lưu địa chỉ đó. Vì vậy, đây không phải là một bộ nhớ trên đầu? Điều này được bồi thường như thế nào? Là con trỏ được sử dụng trong thời gian ứng dụng bộ nhớ thấp quan trọng?


11
Lợi ích của việc phân bổ bộ nhớ động lớn hơn nhiều so với chi phí của con trỏ.
James McLeod

32
Bạn nghĩ các ngôn ngữ khác (Java, C #, ...) lưu trữ các tham chiếu đến các đối tượng như thế nào? (Gợi ý: họ sử dụng con trỏ).
Erik Eidt

6
Một con trỏ có thể ngồi trong các thanh ghi hoặc được truyền làm đối số. Trong cả hai trường hợp không có chi phí bộ nhớ rõ ràng . Và con trỏ có thể được tính toán (ví dụ: số học con trỏ, hàm trả về con trỏ, v.v.)
Basile Starynkevitch

9
Làm thế nào về việc vượt qua một đối số cấu trúc (lớn) theo địa chỉ? Nếu bạn tính đó là một biến con trỏ, thì không thể tránh khỏi nhiều thuật toán và sử dụng ít không gian hơn nhiều so với việc chuyển cấu trúc theo giá trị!
PJTraill

4
Điều này có cảm giác của một số bài tập về nhà. Câu hỏi được thiết kế để khám phá nếu câu trả lời hiểu con trỏ và cách chúng được sử dụng.
Michael Shaw

Câu trả lời:


34

Trên thực tế, chi phí không thực sự nằm trong 4 hoặc 8 byte cần thiết để lưu trữ con trỏ. Hầu hết các con trỏ lần được sử dụng để cấp phát bộ nhớ động , nghĩa là chúng ta gọi một hàm để cấp phát một khối bộ nhớ và hàm này trả về cho chúng ta một con trỏ trỏ đến khối bộ nhớ đó. Khối mới này và bản thân nó đại diện cho một chi phí đáng kể.

Bây giờ, bạn không phải tham gia vào việc cấp phát bộ nhớ để sử dụng một con trỏ: Bạn có thể có một mảng được intkhai báo tĩnh hoặc trên ngăn xếp và bạn có thể sử dụng một con trỏ thay vì một chỉ mục để truy cập vào ints, và đó là tất cả đều rất đẹp và đơn giản và hiệu quả Không cần phân bổ bộ nhớ và con trỏ thường sẽ chiếm chính xác không gian trong bộ nhớ như một chỉ số nguyên.

Ngoài ra, như Joshua Taylor nhắc nhở chúng ta trong một bình luận, con trỏ được sử dụng để vượt qua một cái gì đó bằng cách tham khảo. Ví dụ, struct foo f; init_foo(&f);sẽ phân bổ f trên ngăn xếp và sau đó gọi init_foo()bằng một con trỏ tới đó struct. Điều đó rất phổ biến. (Chỉ cần cẩn thận không để vượt qua những gợi ý "đi lên".) Trong C ++ bạn có thể thấy điều này được thực hiện với một "tài liệu tham khảo" ( foo&) thay vì một con trỏ, nhưng tài liệu tham khảo là gì, nhưng gợi ý rằng bạn không thể thay đổi, và họ chiếm cùng dung lượng bộ nhớ.

Nhưng lý do chính tại sao con trỏ được sử dụng là để cấp phát bộ nhớ động và điều này được thực hiện để giải quyết các vấn đề không thể giải quyết bằng cách khác. Đây là một ví dụ đơn giản: Hãy tưởng tượng bạn muốn đọc toàn bộ nội dung của một tập tin. Bạn sẽ cất chúng ở đâu? Nếu bạn thử với bộ đệm có kích thước cố định, thì bạn sẽ chỉ có thể đọc các tệp không dài hơn bộ đệm đó. Nhưng bằng cách sử dụng cấp phát bộ nhớ, bạn có thể phân bổ càng nhiều bộ nhớ cần thiết để đọc tệp, sau đó tiến hành đọc nó.

Ngoài ra, C ++ là một ngôn ngữ hướng đối tượng và có một số khía cạnh nhất định của OOP như sự trừu tượng chỉ có thể đạt được bằng cách sử dụng các con trỏ. Ngay cả các ngôn ngữ như Java và C # cũng sử dụng rộng rãi các con trỏ, chúng chỉ không cho phép bạn thao tác trực tiếp với các con trỏ, để ngăn bạn thực hiện các công cụ nguy hiểm với chúng, tuy nhiên, các ngôn ngữ này chỉ bắt đầu có ý nghĩa khi bạn có nhận ra rằng đằng sau hậu trường mọi thứ được thực hiện bằng cách sử dụng con trỏ.

Vì vậy, con trỏ không chỉ được sử dụng trong các ứng dụng bộ nhớ thấp, thời gian quan trọng, chúng được sử dụng ở mọi nơi .


5
"lý do chính tại sao con trỏ được sử dụng là để cấp phát bộ nhớ động" Đó có thể là trường hợp trong C ++, nhưng không nhất thiết phải ở C. Trong C, con trỏ là cách duy nhất để bạn vượt qua điều gì đó bằng cách tham chiếu. Nếu bạn không muốn sao chép toàn bộ cấu trúc, bạn sẽ cần một con trỏ tới nó. Điều đó vẫn có ý nghĩa ngay cả khi bạn không thực hiện phân bổ động nào cả. Ví dụ, struct foo f; init_foo(&f);sẽ phân bổ ftrên ngăn xếp và sau đó gọi init_foobằng một con trỏ tới cấu trúc đó. Điều đó rất phổ biến. (Chỉ cần cẩn thận để không vượt qua những con trỏ "hướng lên".)
Joshua Taylor

@JoshuaTaylor điều đó rất đúng, tôi đã quên nó rồi. Tôi có thể sửa đổi nó để trả lời của tôi? (Đây được coi là thực hành tốt đối với các lập trình viên SE vì các bình luận là phù du, trong khi câu trả lời thì không.)
Mike Nakis

Bằng mọi cách thêm nó vào câu trả lời của bạn. :)
Joshua Taylor

2
Điều này thể hiện chi phí đáng kể trong chính nó, bởi vì khối này sẽ có một tiêu đề (ẩn) thường sẽ lớn bằng một vài con trỏ. => Trên thực tế, các triển khai hiện đại malloccó chi phí tiêu đề RẤT THẤP khi chúng phân cụm các khối được phân bổ trong "nhóm". Mặt khác, điều này chuyển thành phân bổ quá mức nói chung: bạn yêu cầu 35 byte và nhận 64 (không có kiến ​​thức của bạn) do đó lãng phí 29 ...
Matthieu M.

1
@MikeNakis: Có vẻ tốt hơn, cảm ơn vì đã gắn bó với tôi :)
Matthieu M.

35

Vì vậy, đây không phải là một bộ nhớ trên đầu?

Chắc chắn, một địa chỉ bổ sung (thường là 4/8 byte tùy thuộc vào bộ xử lý).

Điều này được bồi thường như thế nào?

Không phải vậy. Nếu bạn cần sự quyết định cần thiết cho con trỏ, thì bạn phải trả tiền cho nó.

Là con trỏ được sử dụng trong thời gian ứng dụng bộ nhớ thấp quan trọng?

Tôi đã không làm nhiều việc ở đó, nhưng tôi sẽ cho là như vậy. Truy cập con trỏ là một khía cạnh cơ bản của lập trình lắp ráp. Phải mất một lượng nhỏ bộ nhớ và các thao tác con trỏ rất nhanh - ngay cả trong bối cảnh của các loại ứng dụng này.


1
@DavidGrinberg Điều đó chỉ giả sử không có tối ưu hóa giá trị trả về .
Darkhogg

3
Tôi đã sử dụng các con trỏ khi viết các ứng dụng TSR cho DOS phải phù hợp với 15k trở lại trong những năm tám mươi. Vì vậy, có, chúng được sử dụng trong các ứng dụng bộ nhớ thấp.
Gort Robot

1
@StevenBurnap Bạn gọi bộ nhớ thấp 15k cho ứng dụng TSR? Tôi đã từng viết một công cụ TSR chỉ tiêu thụ 16 byte bộ nhớ.
kasperd

22
@kasperd: Và trở lại vào thời của tôi, chúng tôi chỉ có số không
Jack


11

Tôi hoàn toàn không có guồng quay tương tự như Telastyn.

Toàn cầu hệ thống trong một bộ xử lý nhúng có thể được xử lý bằng các địa chỉ cụ thể, được mã hóa cứng.

Các quả cầu trong một chương trình sẽ được xử lý như một phần bù từ một con trỏ đặc biệt trỏ đến vị trí trong bộ nhớ nơi lưu trữ toàn cục và số liệu thống kê.

Các biến cục bộ xuất hiện khi một hàm được nhập và được xử lý như một phần bù từ một con trỏ đặc biệt khác, thường được gọi là "con trỏ khung". Điều này bao gồm các đối số cho chức năng. Nếu bạn cẩn thận về các lần đẩy và bật với con trỏ ngăn xếp, bạn có thể loại bỏ con trỏ khung và truy cập các biến cục bộ trực tiếp từ con trỏ ngăn xếp.

Vì vậy, bạn trả tiền cho việc xác định con trỏ cho dù bạn đang sải bước qua một mảng hay chỉ lấy một số biến cục bộ hoặc toàn cầu không đáng kể. Nó chỉ dựa trên một con trỏ khác nhau, tùy thuộc vào loại biến nào. Mã được biên dịch tốt sẽ giữ con trỏ đó trong thanh ghi CPU, thay vì tải lại mỗi lần nó được sử dụng.


6

Phải, tất nhiên. Nhưng đó là một hành động cân bằng.

Các ứng dụng bộ nhớ thấp thường được xây dựng mang ý nghĩa đánh đổi giữa chi phí của một vài biến con trỏ so với tổng phí của chương trình lớn (phải được lưu trữ trong bộ nhớ, hãy nhớ!) Nếu không thể sử dụng con trỏ .

Việc xem xét này áp dụng cho tất cả các chương trình, bởi vì không ai muốn xây dựng một mớ hỗn độn khủng khiếp, không thể nhầm lẫn với mã trùng lặp bên trái và trung tâm, lớn hơn gấp hai mươi lần so với yêu cầu.


5

Trong các ngôn ngữ như C và C ++, trong khi sử dụng con trỏ tới các biến, chúng ta cần thêm một vị trí bộ nhớ để lưu địa chỉ đó. Vì vậy, đây không phải là một bộ nhớ trên đầu?

Bạn cho rằng con trỏ cần được lưu trữ. Không phải trường hợp nào cũng vậy. Mỗi biến được lưu trữ tại một số địa chỉ bộ nhớ. Nói rằng bạn có một longtuyên bố là long n = 5L;. Điều này phân bổ lưu trữ cho ntại một số địa chỉ. Chúng ta có thể sử dụng địa chỉ đó để làm những việc ưa thích như *((char *) &n) = (char) 0xFF;thao tác các phần của n. Địa chỉ nkhông được lưu trữ ở bất cứ đâu như một chi phí phụ.

Điều này được bồi thường như thế nào?

Ngay cả khi con trỏ được lưu trữ rõ ràng (ví dụ: trong các cấu trúc dữ liệu như danh sách), cấu trúc dữ liệu kết quả thường thanh lịch hơn (đơn giản hơn, dễ hiểu hơn, dễ xử lý hơn, v.v.) so với cấu trúc dữ liệu tương đương không có con trỏ.

Là con trỏ được sử dụng trong thời gian ứng dụng bộ nhớ thấp quan trọng?

Vâng. Các thiết bị sử dụng bộ điều khiển vi mô thường chứa rất ít bộ nhớ nhưng phần sụn có thể sử dụng các con trỏ để xử lý các vectơ ngắt hoặc quản lý bộ đệm, v.v.


Một số biến không được lưu trong bộ nhớ, mà chỉ trong các thanh ghi
Basile Starynkevitch

@BasileStarynkevitch: Bạn có thể đề xuất các biến ở trong sổ đăng ký, nhưng trình biên dịch không bắt buộc phải làm như vậy. Trừ khi bạn đang lập trình trong trình biên dịch chương trình, bạn thực sự không có mức kiểm soát đó đối với việc lưu trữ ngay lập tức. Và ngay cả trong trình biên dịch chương trình, bất kỳ chương trình con nào bạn gọi sẽ có khả năng làm đổ các thanh ghi lên ngăn xếp để nó có thể sử dụng chúng cho các biến của nó . Vì vậy, đối với bất kỳ chương trình không tầm thường nào, hầu như đảm bảo các biến của bạn sẽ dành ít nhất một chút thời gian trong bộ nhớ.
TMN

Trình biên dịch có thể chỉ được phép lưu trữ một số biến cục bộ trong các thanh ghi và hầu hết các trình biên dịch tối ưu hóa đang làm điều đó (thử gcc -fverbose-asm -S -O2biên dịch một số mã C)
Basile Starynkevitch

@BasileStarynkevitch Tôi không chắc chắn về điểm bạn đang cố gắng thực hiện với quan sát rằng các biến có thể được lưu trữ hoàn toàn trong các thanh ghi. Bạn có thể vui lòng giải thích?
Lawrence

5

Có một con trỏ chắc chắn tiêu tốn một số chi phí, nhưng bạn cũng có thể thấy sự tăng giá. Con trỏ giống như chỉ mục. Trong C, bạn có thể sử dụng các cấu trúc dữ liệu phức tạp như chuỗi và cấu trúc chỉ do con trỏ.

Trong thực tế, giả sử bạn muốn truyền một biến bằng tham chiếu thì việc duy trì một con trỏ dễ dàng hơn là sao chép toàn bộ cấu trúc và đồng bộ hóa các thay đổi giữa chúng (ngay cả khi sao chép chúng, bạn sẽ cần con trỏ). Làm thế nào bạn sẽ đối phó với phân bổ bộ nhớ không liền kề và phân bổ lại mà không có con trỏ?

Ngay cả các biến thông thường của bạn cũng có một mục trong bảng ký hiệu lưu trữ địa chỉ nơi biến của bạn đang hướng tới. Vì vậy, tôi không nghĩ rằng nó tạo ra nhiều chi phí về bộ nhớ (chỉ 4 hoặc 8 byte). Ngay cả các ngôn ngữ như java cũng sử dụng các con trỏ bên trong (tham chiếu), chúng chỉ không cho phép bạn thao tác với chúng vì nó sẽ làm cho JVM kém an toàn hơn.

Bạn chỉ nên sử dụng các con trỏ khi bạn không có lựa chọn nào khác như thiếu các kiểu dữ liệu, cấu trúc (trong c) vì sử dụng các con trỏ có thể dẫn đến lỗi nếu không được xử lý đúng cách và tương đối khó gỡ lỗi hơn.


3

Vì vậy, đây không phải là một bộ nhớ trên đầu?

Có .... không ... có lẽ?

Đây là một câu hỏi khó xử vì hãy tưởng tượng phạm vi địa chỉ bộ nhớ trên máy và một phần mềm cần liên tục theo dõi vị trí của mọi thứ trong bộ nhớ theo cách không thể gắn với ngăn xếp.

Ví dụ: hãy tưởng tượng một trình phát nhạc trong đó tệp nhạc được tải bởi người dùng ấn nút và dỡ khỏi bộ nhớ dễ bay hơi khi người dùng cố tải một tệp nhạc khác.

Làm thế nào để chúng tôi theo dõi nơi dữ liệu âm thanh được lưu trữ? Chúng tôi cần một địa chỉ bộ nhớ cho nó. Chương trình không chỉ cần theo dõi đoạn dữ liệu âm thanh trong bộ nhớ mà còn ở nơi nó nằm trong bộ nhớ. Vì vậy, chúng ta cần giữ xung quanh một địa chỉ bộ nhớ (nghĩa là một con trỏ). Và kích thước của bộ lưu trữ cần thiết cho địa chỉ bộ nhớ sẽ khớp với phạm vi địa chỉ của máy (ví dụ: con trỏ 64 bit cho phạm vi địa chỉ 64 bit).

Vì vậy, đó là loại "có", nó yêu cầu lưu trữ để theo dõi địa chỉ bộ nhớ, nhưng không phải là chúng ta có thể tránh nó cho bộ nhớ được phân bổ động của loại này.

Điều này được bồi thường như thế nào?

Nói về kích thước của chính con trỏ, bạn có thể tránh chi phí trong một số trường hợp bằng cách sử dụng ngăn xếp, ví dụ: Trong trường hợp đó, trình biên dịch có thể tạo hướng dẫn mã hóa hiệu quả địa chỉ bộ nhớ tương đối, tránh chi phí của con trỏ. Tuy nhiên, điều này khiến bạn dễ bị chồng chất nếu bạn làm điều này cho các phân bổ lớn, có kích thước thay đổi và cũng có xu hướng không thực tế (nếu không hoàn toàn không thể) để thực hiện cho một loạt các nhánh phức tạp do đầu vào của người dùng điều khiển (như trong ví dụ âm thanh ở trên).

Một cách khác là sử dụng các cấu trúc dữ liệu liền kề hơn. Ví dụ, một chuỗi dựa trên mảng có thể được sử dụng thay vì danh sách liên kết đôi đòi hỏi hai con trỏ trên mỗi nút. Chúng ta cũng có thể sử dụng kết hợp cả hai giống như một danh sách không được kiểm soát, chỉ lưu trữ các con trỏ ở giữa mỗi nhóm N phần tử liền kề.

Là con trỏ được sử dụng trong thời gian ứng dụng bộ nhớ thấp quan trọng?

Có, rất phổ biến, vì nhiều ứng dụng quan trọng về hiệu năng được viết bằng C hoặc C ++, bị chi phối bởi việc sử dụng con trỏ (chúng có thể nằm sau một con trỏ thông minh hoặc một thùng chứa như std::vectorhoặc std::string, nhưng các cơ chế cơ bản luồn xuống một con trỏ được sử dụng để theo dõi địa chỉ đến một khối bộ nhớ động).

Bây giờ trở lại câu hỏi này:

Điều này được bồi thường như thế nào? (Phần hai)

Con trỏ thường rất rẻ trừ khi bạn lưu trữ như một triệu trong số chúng (vẫn là 8 megabyte trên máy 64 bit).

* Lưu ý như Ben đã chỉ ra rằng 8 megs "sởi" vẫn là kích thước của bộ đệm L3. Ở đây tôi đã sử dụng "bệnh sởi" nhiều hơn theo nghĩa tổng sử dụng DRAM và kích thước tương đối điển hình cho khối bộ nhớ mà việc sử dụng con trỏ khỏe mạnh sẽ chỉ ra.

Con trỏ đắt ở đâu không phải là con trỏ mà là:

  1. Phân bổ bộ nhớ động. Phân bổ bộ nhớ động có xu hướng đắt tiền vì nó phải trải qua cấu trúc dữ liệu cơ bản (ví dụ: bộ cấp phát bạn bè hoặc bản sàn). Mặc dù những thứ này thường được tối ưu hóa cho đến chết, chúng có mục đích chung và được thiết kế để xử lý các khối có kích thước thay đổi, đòi hỏi chúng phải thực hiện ít nhất một chút công việc giống như "tìm kiếm" (mặc dù nhẹ và thậm chí có thể là thời gian không đổi) tìm một bộ miễn phí các trang liền kề trong bộ nhớ.

  2. Truy cập bộ nhớ. Điều này có xu hướng là chi phí lớn hơn để lo lắng về. Bất cứ khi nào chúng tôi truy cập bộ nhớ được cấp phát lần đầu tiên, sẽ có một lỗi trang bắt buộc cũng như bộ nhớ cache bị mất khi di chuyển bộ nhớ xuống hệ thống phân cấp bộ nhớ và xuống một thanh ghi.

Truy cập bộ nhớ

Truy cập bộ nhớ là một trong những khía cạnh quan trọng nhất của hiệu suất ngoài thuật toán. Rất nhiều lĩnh vực quan trọng về hiệu năng như công cụ trò chơi AAA tập trung rất nhiều năng lượng của họ vào việc tối ưu hóa theo định hướng dữ liệu, giúp cải thiện các kiểu và bố cục truy cập bộ nhớ hiệu quả hơn.

Một trong những khó khăn hiệu năng lớn nhất của các ngôn ngữ cấp cao hơn muốn phân bổ riêng từng loại do người dùng xác định thông qua bộ thu gom rác, ví dụ, chúng có thể phân mảnh bộ nhớ khá nhiều. Điều này có thể đặc biệt đúng nếu không phải tất cả các đối tượng được phân bổ cùng một lúc.

Trong các trường hợp đó, nếu bạn lưu trữ danh sách một triệu trường hợp của loại đối tượng do người dùng xác định, việc truy cập các trường hợp đó theo trình tự trong một vòng lặp có thể khá chậm vì nó tương tự như danh sách một triệu con trỏ trỏ đến các vùng bộ nhớ khác nhau. Trong những trường hợp đó, kiến ​​trúc muốn tìm nạp bộ nhớ ở cấp trên, cấp độ chậm hơn, lớn hơn của hệ thống phân cấp theo khối lớn, được căn chỉnh với hy vọng rằng dữ liệu xung quanh trong các khối đó sẽ được truy cập trước khi bị trục xuất. Khi mỗi đối tượng trong danh sách như vậy được phân bổ riêng biệt, thì cuối cùng chúng ta sẽ trả tiền cho nó bằng các lỗi nhớ cache khi mỗi lần lặp tiếp theo có thể phải tải từ một khu vực hoàn toàn khác trong bộ nhớ mà không có đối tượng liền kề nào được truy cập trước khi bị trục xuất.

Ngày nay, rất nhiều trình biên dịch cho các ngôn ngữ như vậy đang thực hiện công việc tuyệt vời khi lựa chọn hướng dẫn và phân bổ đăng ký, nhưng việc thiếu kiểm soát trực tiếp hơn đối với việc quản lý bộ nhớ ở đây có thể là kẻ giết người (mặc dù thường ít bị lỗi hơn) và vẫn tạo ra các ngôn ngữ như C và C ++ khá phổ biến.

Tối ưu hóa truy cập con trỏ gián tiếp

Trong các tình huống quan trọng nhất về hiệu năng, các ứng dụng thường sử dụng nhóm bộ nhớ chứa bộ nhớ từ các khối liền kề để cải thiện vị trí tham chiếu. Trong các trường hợp như vậy, ngay cả một cấu trúc được liên kết như cây hoặc danh sách được liên kết cũng có thể được tạo thân thiện với bộ đệm với điều kiện là bố cục bộ nhớ của các nút của nó liền kề nhau về bản chất. Điều này có hiệu quả làm cho hội thảo con trỏ rẻ hơn, mặc dù gián tiếp bằng cách cải thiện địa phương của tài liệu tham khảo khi tham gia hội nghị.

Đuổi theo xung quanh

Giả sử chúng ta có một danh sách liên kết đơn lẻ như:

Foo->Bar->Baz->null

Vấn đề là nếu chúng ta phân bổ tất cả các nút này một cách riêng biệt cho một công cụ phân bổ mục đích chung (và có thể không phải tất cả cùng một lúc), bộ nhớ thực tế có thể bị phân tán phần nào như thế này (sơ đồ đơn giản):

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

Khi chúng tôi bắt đầu đuổi theo con trỏ xung quanh và truy cập vào Foonút, chúng tôi bắt đầu với một lỗi bắt buộc (và có thể là lỗi trang) di chuyển một đoạn từ vùng nhớ của nó từ vùng nhớ chậm hơn sang vùng nhớ nhanh hơn, như vậy:

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

Điều này khiến chúng tôi lưu trữ bộ đệm (có thể cả trang) một vùng bộ nhớ chỉ để truy cập vào một phần của nó và đuổi phần còn lại khi chúng tôi đuổi theo con trỏ xung quanh danh sách này. Tuy nhiên, bằng cách kiểm soát bộ cấp phát bộ nhớ, chúng ta có thể phân bổ một danh sách liên tục như vậy:

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

... và do đó cải thiện đáng kể tốc độ mà chúng ta có thể hủy bỏ các con trỏ này và xử lý các điểm của chúng. Vì vậy, mặc dù rất gián tiếp, chúng ta có thể tăng tốc truy cập con trỏ theo cách này. Tất nhiên, nếu chúng ta chỉ lưu trữ chúng liên tục trong một mảng, chúng ta sẽ không gặp vấn đề này ngay từ đầu, nhưng bộ cấp phát bộ nhớ ở đây cho phép chúng ta kiểm soát rõ ràng bố cục bộ nhớ có thể tiết kiệm được ngày khi cần cấu trúc được liên kết.

* Lưu ý: đây là sơ đồ và thảo luận rất đơn giản về phân cấp bộ nhớ và địa phương của tài liệu tham khảo, nhưng hy vọng nó phù hợp với cấp độ của câu hỏi.


1
"8 triệu megabyte" là kích thước điển hình của toàn bộ bộ đệm L3 trên CPU hiện đại
Ben Voigt

1
@BenVoigt Tôi sẽ cố gắng định hướng điều đó một chút. Tất nhiên đó sẽ là khủng khiếp nếu mỗi con trỏ đã được trỏ đến một đoạn 32-bit của bộ nhớ, ví dụ

@BenVoigt Đã thêm chú thích để cố gắng làm rõ phần đó!

Sự làm rõ đó có vẻ tốt.
Ben Voigt

1

Vì vậy, đây không phải là một bộ nhớ trên đầu?

Nó thực sự là một bộ nhớ trên đầu, nhưng là một cái rất nhỏ (đến mức không đáng kể).

Điều này được bồi thường như thế nào?

Nó không được bồi thường. Bạn cần nhận ra rằng việc truy cập dữ liệu thông qua một con trỏ (hội thảo một con trỏ) là cực kỳ nhanh (nếu tôi nhớ chính xác, nó chỉ sử dụng một hướng dẫn lắp ráp cho mỗi lần đăng ký). Nó đủ nhanh để trong nhiều trường hợp bạn có thể thay thế nhanh nhất.

Là con trỏ được sử dụng trong thời gian ứng dụng bộ nhớ thấp quan trọng?

Vâng.


0

Bạn chỉ cần sử dụng bộ nhớ thêm (thông thường 4-8 byte cho mỗi con trỏ) trong khi bạn cần con trỏ đó. Có nhiều kỹ thuật làm cho giá cả phải chăng hơn.

Kỹ thuật cơ bản nhất giúp con trỏ trở nên mạnh mẽ là bạn không cần phải giữ mọi con trỏ. Đôi khi bạn có thể sử dụng một thuật toán để xây dựng một con trỏ từ một con trỏ đến một thứ khác. Ví dụ tầm thường nhất của điều này là số học mảng. Nếu bạn phân bổ một mảng gồm 50 số nguyên, bạn không cần giữ 50 con trỏ, mỗi con số cho mỗi số nguyên. Bạn thường theo dõi một con trỏ (con trỏ đầu tiên) và sử dụng số học con trỏ để tạo ra những con trỏ khác. Đôi khi bạn có thể tạm thời giữ một trong những con trỏ đó cho một phần tử cụ thể của mảng, nhưng chỉ khi bạn cần nó. Khi bạn đã hoàn tất, bạn có thể loại bỏ nó, miễn là bạn giữ đủ thông tin để tạo lại nó sau này, nếu bạn cần. Điều này nghe có vẻ tầm thường, nhưng nó chính xác là loại công cụ bảo tồn mà bạn '

Trong các tình huống bộ nhớ cực kỳ chặt chẽ, điều này có thể được sử dụng để giảm thiểu chi phí. Nếu bạn đang làm việc trong một không gian bộ nhớ rất chật, bạn thường có ý thức tốt về số lượng đối tượng bạn cần thao tác. Thay vì phân bổ một loạt các số nguyên cùng một lúc và giữ các con trỏ đầy đủ cho chúng, bạn có thể tận dụng kiến ​​thức nhà phát triển của mình rằng bạn sẽ không bao giờ có nhiều hơn 256 số nguyên trong thuật toán cụ thể này. Trong trường hợp đó, bạn có thể giữ một con trỏ đến số nguyên đầu tiên và theo dõi chỉ mục bằng cách sử dụng char (1 byte) thay vì sử dụng con trỏ đầy đủ (4/8 byte). Bạn cũng có thể sử dụng các thủ thuật thuật toán để tạo một số chỉ số này một cách nhanh chóng.

Loại trí nhớ có lương tâm này rất phổ biến trong quá khứ. Ví dụ, các trò chơi NES sẽ phụ thuộc rất nhiều vào khả năng nhồi nhét dữ liệu của họ và tạo ra các con trỏ theo thuật toán thay vì phải lưu trữ tất cả chúng bán buôn.

Các tình huống bộ nhớ cực đoan cũng có thể khiến người ta phải làm những việc như phân bổ tất cả các không gian bạn hoạt động vào thời gian biên dịch. Sau đó, con trỏ bạn phải lưu trữ vào bộ nhớ đó được lưu trữ trong chương trình chứ không phải dữ liệu. Trong nhiều tình huống hạn chế bộ nhớ, bạn có bộ nhớ dữ liệu và chương trình riêng biệt (thường là ROM so với RAM), do đó bạn có thể điều chỉnh cách bạn sử dụng thuật toán của mình để đẩy con trỏ vào bộ nhớ chương trình đó.

Về cơ bản, bạn không thể thoát khỏi tất cả các chi phí. Tuy nhiên, bạn có thể kiểm soát nó. Bằng cách sử dụng các kỹ thuật thuật toán, bạn có thể giảm thiểu số lượng con trỏ bạn có thể lưu trữ. Nếu bạn tình cờ sử dụng con trỏ vào bộ nhớ động, bạn sẽ không bao giờ nhận được dưới mức chi phí giữ 1 con trỏ cho điểm bộ nhớ động đó, vì đó là lượng thông tin tối thiểu cần thiết để truy cập bất cứ thứ gì trong khối bộ nhớ đó. Tuy nhiên, trong các tình huống hạn chế bộ nhớ siêu chặt, đây có thể là trường hợp đặc biệt (bộ nhớ động và hạn chế bộ nhớ siêu chặt có xu hướng không xuất hiện trong cùng một tình huống).


0

Trong nhiều tình huống con trỏ thực sự tiết kiệm bộ nhớ. Một thay thế phổ biến cho việc sử dụng con trỏ là tạo một bản sao của cấu trúc dữ liệu. Một bản sao đầy đủ của cấu trúc dữ liệu sẽ lớn hơn một con trỏ.

Một ví dụ về ứng dụng quan trọng về thời gian là ngăn xếp mạng. Một ngăn xếp mạng tốt sẽ được thiết kế thành "không sao chép" - và để làm được điều này đòi hỏi phải sử dụng con trỏ thông minh.

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.