PHP phá hủy bộ nhớ trống đối tượng

Các biến đại diện cho không gian lưu trữ trong bộ nhớ của máy tính. Mỗi biến trình bày một tên thuận tiện như số hoặc kết quả trong mã nguồn. Đằng sau hậu trường trong thời gian chạy, mỗi biến sử dụng một vùng bộ nhớ của máy tính để lưu trữ giá trị của nó

Tuy nhiên, không phải mọi biến trong chương trình đều có vùng bộ nhớ được gán cố định, thay vào đó, các ngôn ngữ hiện đại rất thông minh trong việc chỉ cấp bộ nhớ cho một biến khi cần thiết. Khi chúng tôi sử dụng thuật ngữ phân bổ, chúng tôi chỉ ra rằng biến được cung cấp một vùng bộ nhớ để lưu trữ giá trị của nó. Một biến bị hủy cấp phát khi hệ thống lấy lại bộ nhớ từ biến đó, vì vậy nó không còn vùng để lưu trữ giá trị của nó

Đối với một biến, khoảng thời gian từ khi cấp phát cho đến khi hủy cấp phát được gọi là thời gian tồn tại của nó. Lỗi liên quan đến bộ nhớ phổ biến nhất là sử dụng biến bị hủy cấp phát. Đối với các biến cục bộ, các ngôn ngữ hiện đại sẽ tự động bảo vệ khỏi lỗi này. Nói cách khác, hầu hết thời gian, các biến cục bộ tự động xuất hiện khi chúng ta cần chúng và chúng tự động biến mất khi chúng ta hoàn thành chúng. Tuy nhiên, với con trỏ, người lập trình phải đảm bảo rằng việc phân bổ được xử lý chính xác

Biến cục bộ [hoặc tự động]

Các biến phổ biến nhất mà chúng tôi sử dụng là các biến cục bộ trong các hàm, chẳng hạn như biến số và kết quả trong hàm sau. Tất cả các biến cục bộ và tham số được kết hợp với nhau được gọi là bộ nhớ cục bộ hoặc chỉ cục bộ của nó

// Local storage example
int Square[int number] {
     int result;
     result = number * number;
     return result;
}

Các biến được gọi là cục bộ để thể hiện ý tưởng rằng thời gian tồn tại của chúng được gắn với hàm nơi chúng được khai báo. Bất cứ khi nào hàm chạy, các biến cục bộ của nó được cấp phát. Khi chức năng thoát, các địa phương của nó được giải phóng

Đối với ví dụ trên, điều đó có nghĩa là khi hàm Square[] được gọi, bộ nhớ cục bộ được phân bổ cho số và kết quả. Khi chức năng cuối cùng thoát, bộ nhớ cục bộ của nó bị hủy cấp phát

Người dân địa phương 101

  1. Khi một chức năng được gọi, bộ nhớ được phân bổ cho tất cả các địa phương của nó. Nói cách khác, khi luồng điều khiển bắt đầu { cho hàm, tất cả các cục bộ của nó được cấp phát bộ nhớ. Các tham số như số và biến cục bộ như kết quả trong ví dụ trên đều được tính là cục bộ. Sự khác biệt duy nhất giữa tham số và biến cục bộ là tham số bắt đầu với giá trị được sao chép từ trình gọi trong khi biến cục bộ bắt đầu với giá trị ban đầu ngẫu nhiên

  2. Bộ nhớ cho các cục bộ tiếp tục được phân bổ miễn là luồng điều khiển nằm trong chức năng sở hữu. Các cục bộ tiếp tục tồn tại ngay cả khi chức năng tạm thời tắt luồng điều khiển bằng cách gọi một chức năng khác. Người dân địa phương tồn tại không bị xáo trộn thông qua tất cả những điều này

  3. Cuối cùng, khi chức năng kết thúc và thoát, các cục bộ của nó sẽ được giải phóng. Điều này có ý nghĩa theo một cách nào đó [giả sử người dân địa phương bằng cách nào đó vẫn tiếp tục tồn tại] thì làm sao mã có thể đề cập đến họ? . Một khi luồng điều khiển rời khỏi cơ thể đó, không có cách nào để tham khảo các địa phương ngay cả khi chúng được phân bổ. Người dân địa phương đó chỉ khả dụng [trong phạm vi] trong chức năng sở hữu của họ

Các tham số cục bộ về cơ bản là các bản sao cục bộ của thông tin từ người gọi. Điều này còn được gọi là chuyển theo giá trị

Các tham số là các biến cục bộ được khởi tạo với thao tác gán [=] từ người gọi. Người gọi không chia sẻ giá trị tham số với callee. Nói cách khác, callee đang nhận bản sao của chính nó

Điều này có lợi thế là người được gọi có thể thay đổi bản sao cục bộ của nó mà không ảnh hưởng đến người gọi. Sự độc lập này là tốt vì nó tách biệt hoạt động của các chức năng người gọi và người được gọi, tuân theo các quy tắc của công nghệ phần mềm tốt [giữ các thành phần riêng biệt càng độc lập càng tốt]

Tuy nhiên, vì các địa phương là bản sao của các tham số người gọi, nên chúng không cung cấp phương tiện liên lạc từ người được gọi trở lại người gọi. Đây là nhược điểm của lợi thế độc lập. Ngoài ra, đôi khi tạo bản sao của một giá trị rất tốn kém

Một lỗi phổ biến. một hàm trả về một con trỏ tới một biến cục bộ

Đoạn mã sau có lỗi phổ biến nhất trong đó

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}

local_pointer[] thực sự ổn khi nó đang chạy. Sự cố xảy ra với người gọi sau khi local_pointer[] thoát. local_pointer[] trả về một con trỏ tới một int, nhưng int đó được phân bổ ở đâu?

Vấn đề là local int, temp, chỉ được phân bổ khi local_pointer[] đang chạy. Khi local_pointer[] thoát, tất cả các địa phương của nó bị hủy. Vì vậy, người gọi chỉ còn lại một con trỏ tới một biến được giải phóng. local_pointer[]'s local được giải phóng khi nó thoát

Việc local_pointer[] trả về một con trỏ tới bộ nhớ sắp bị hủy cấp phát là không chính xác. Về cơ bản, chúng tôi đang chạy vào giới hạn thời gian tồn tại của các biến cục bộ. Chúng tôi muốn int tồn tại, nhưng nó sẽ tự động bị hủy bỏ. Không phải tất cả các cách sử dụng & giữa các hàm đều không chính xác - chỉ khi được sử dụng để chuyển con trỏ trở lại trình gọi

Khi chúng tôi thực sự chạy mã nhỏ, nó có vẻ ổn. Nhưng lỗi vẫn ẩn nấp ở đó. Chúng tôi có thể thấy hiệu quả ngay lập tức khi mã trở nên phức tạp hơn. Nói cách khác, trong trường hợp khi hệ thống lấy lại vùng nhớ của con trỏ

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp;]; 
}

void f[] { int array[10000] = {1};}

int main[]
{
	int* ptr = local_pointer[];
	f[];
	return 0;
}

Lưu ý rằng lỗi này xảy ra khi & chuyển một con trỏ tới bộ nhớ cục bộ từ đối tượng được gọi trở lại đối tượng gọi của nó. Khi đối tượng được gọi thoát, bộ nhớ cục bộ của nó bị hủy cấp phát và do đó con trỏ không còn điểm trỏ nữa

Tuy nhiên, nếu chúng ta sử dụng & để chuyển một con trỏ từ người gọi sang người được gọi thì không sao. Con trỏ vẫn hợp lệ để người được gọi sử dụng vì người gọi cục bộ tiếp tục tồn tại trong khi người được gọi đang chạy. Đối tượng mà con trỏ trỏ tới sẽ vẫn hợp lệ do ràng buộc đơn giản mà người gọi chỉ có thể thoát ra sau khi hàm được gọi của nó thoát ra. Sử dụng & để chuyển một con trỏ tới bộ nhớ cục bộ từ người gọi đến người được gọi là tốt. Trường hợp ngược lại, từ callee đến caller, là khi lỗi xảy ra như trong ví dụ trên

Quản lý bộ nhớ tự động

Trước khi chúng tôi đi vào quản lý bộ nhớ thủ công, có thể tốt hơn là xem quản lý bộ nhớ tự động

Quản lý bộ nhớ tự động có liên quan chặt chẽ với các biến cục bộ. Một biến cục bộ chiếm bộ nhớ mà hệ thống phân bổ khi nó nhìn thấy định nghĩa của biến trong quá trình thực thi. Hệ thống cũng tự động giải phóng bộ nhớ đó ở cuối khối chứa định nghĩa

Các lập trình viên đôi khi mắc lỗi trả về con trỏ không hợp lệ như chúng ta thấy trong ví dụ bên dưới. Một con trỏ trở nên không hợp lệ khi biến tương ứng đã được giải phóng

int * badPointer[] {
	int i = 100;
	return &i;
}

Hàm badPulum[] trả về địa chỉ của biến cục bộ i. Tuy nhiên, khi hàm trả về, thực sự kết thúc việc thực thi khối và giải phóng i. Vì vậy, con trỏ &i; . Thực ra nội dung của biến i đã đúng tại thời điểm hàm trả về. Vấn đề là bộ nhớ cho tôi được phân bổ trong khung ngăn xếp cho badPulum[]. Khi badPulum[] trả về, tất cả bộ nhớ trong khung ngăn xếp của nó sẽ được giải phóng và sẵn sàng để sử dụng bởi các chức năng khác. Tuy nhiên, chức năng vẫn cố gắng trả lại nó. Điều gì sẽ xảy ra?

Nếu chúng tôi khăng khăng trả lại &i;, chúng tôi có thể sử dụng tĩnh

int * pointerToStatic[] {
	static i;
	return &i;
}

Điều này nói rằng tôi là tĩnh và do đó chúng tôi phân bổ nó một lần và chúng tôi không muốn phân bổ nó miễn là mã đang chạy

Nói chung, máy tính có ba vị trí để lưu trữ dữ liệu - bộ nhớ vật lý, bộ đệm và thanh ghi. Bộ nhớ thường lớn so với hai loại lưu trữ còn lại. Mỗi ô nhớ được truy cập bằng một địa chỉ và bộ nhớ không nhất thiết phải liên tiếp. Trên các kiến ​​trúc khác nhau, một số phần nhất định của bộ nhớ được sử dụng để truy cập các thiết bị [phần này được gọi là I/O ánh xạ bộ nhớ]. các phần khác của bộ nhớ thậm chí có thể không được ánh xạ vào bất kỳ bộ nhớ vật lý nào

Bộ nhớ cache là phiên bản nhỏ hơn của bộ nhớ, được lưu trực tiếp trong CPU [bộ nhớ đệm cấp 1] hoặc trên bo mạch chủ [bộ nhớ đệm cấp 2]. Nó lưu trữ một bản sao của các phần được sử dụng gần đây của bộ nhớ chính, ở một vị trí có thể được truy cập nhanh hơn nhiều. Thông thường, do phần cứng ẩn bộ đệm khỏi các chương trình của chúng ta, nên chúng ta không cần chỉ lo lắng về bộ đệm trừ khi chúng ta đang xử lý kernel

Các thanh ghi là các đơn vị lưu trữ bên trong CPU với khả năng truy cập rất nhanh. Chúng có thể được truy cập nhanh hơn nhiều so với bộ nhớ và thường được sử dụng để lưu trữ dữ liệu cần thiết cho một phép tính ngắn, chẳng hạn như nội dung của các biến cục bộ trong hàm hoặc kết quả trung gian của phép tính số học. từ khóa register, khi được sử dụng khi định nghĩa một biến cục bộ, có thể là một gợi ý để trình biên dịch gán biến đó cho một thanh ghi, thay vì cho một ô nhớ. Vì các trình biên dịch hiện đại được tối ưu hóa tốt, nên tốt hơn hết là để trình biên dịch quyết định biến nào sẽ được giữ trong sổ đăng ký

Khi chúng ta nói về quản lý bộ nhớ, đó là về phân bổ vì phân bổ thích hợp là rất quan trọng đối với việc quản lý bộ nhớ

Để cấp phát một đối tượng mới từ kho lưu trữ miễn phí, C sử dụng hàm malloc và C++ sử dụng toán tử new. Việc xác định thời điểm một đối tượng nên được tạo ra là tầm thường và không có vấn đề gì. Tuy nhiên, vấn đề quan trọng là việc xác định khi nào một đối tượng không còn cần thiết nữa và sắp xếp để bộ lưu trữ cơ bản của nó được trả về để nó có thể được sử dụng lại nhằm đáp ứng các yêu cầu bộ nhớ trong tương lai. Trong cấp phát bộ nhớ thủ công, điều này cũng được lập trình viên chỉ định thủ công;

Để biết thêm thông tin về bộ nhớ, vui lòng truy cập và

Nguồn gốc của các vấn đề về bộ nhớ

Việc xử lý bộ nhớ trong C/C++ cho chúng ta khả năng kiểm soát cũng như hiệu suất, nhưng nó đi kèm với những nguy hiểm

  1. Rò rỉ bộ nhớ
    Rò rỉ bộ nhớ xảy ra khi dữ liệu được cấp phát trong thời gian chạy nhưng không được cấp phát lại khi chúng không còn cần thiết. Một chương trình quên giải phóng một khối được cho là bị rò rỉ bộ nhớ, đây có thể là một vấn đề nghiêm trọng hoặc không. Kết quả sẽ là heap dần dần lấp đầy khi tiếp tục có các yêu cầu phân bổ, nhưng không có yêu cầu phân bổ nào để trả lại các khối để sử dụng lại.
    Đối với một chương trình chạy, tính toán thứ gì đó và thoát ngay lập tức, rò rỉ bộ nhớ thường không phải là vấn đề đáng lo ngại. Một chương trình một lần như vậy có thể bỏ qua tất cả các yêu cầu phân bổ của nó và hầu như vẫn hoạt động. Rò rỉ bộ nhớ là một vấn đề nghiêm trọng hơn đối với một chương trình chạy trong một khoảng thời gian không xác định. Trong trường hợp đó, rò rỉ bộ nhớ có thể dần lấp đầy đống cho đến khi các yêu cầu cấp phát không được đáp ứng và chương trình ngừng hoạt động hoặc gặp sự cố.
    Nhiều chương trình thương mại bị rò rỉ bộ nhớ nên khi chạy đủ lâu hoặc với tập dữ liệu lớn sẽ ngốn tài nguyên bộ nhớ và cuối cùng sẽ làm chậm máy do. Sau đó, chúng tôi gặp lỗi với lỗi hết bộ nhớ.
    Việc tìm ra những rò rỉ đó bằng trình gỡ lỗi thông thường là rất khó vì không có dòng mã bị lỗi rõ ràng.
    Thông thường, mã phát hiện và tránh lỗi cho tình trạng lỗi đầy đống không được kiểm tra kỹ lưỡng, chính xác là do trường hợp này hiếm khi gặp phải khi chương trình chạy ngắn - đó là lý do tại sao việc lấp đầy đống thường dẫn đến .
    Hầu hết các trình biên dịch đều có tiện ích gỡ lỗi đống giúp thêm mã gỡ lỗi vào chương trình để theo dõi mọi phân bổ và thỏa thuận. Khi một phân bổ không có phân bổ phù hợp, đó là rò rỉ và trình gỡ lỗi heap có thể giúp chúng tôi tìm thấy chúng.

  2. Lỗi tràn bộ đệm
    Lỗi tràn bộ đệm xảy ra khi bộ nhớ nằm ngoài ranh giới được phân bổ bị ghi đè. Chúng tôi gọi đó là tham nhũng dữ liệu. Điều này thật khó chịu vì nó có thể không hiển thị ở nơi bộ nhớ bị ghi đè. Nó có thể xuất hiện khi chúng ta truy cập địa chỉ bộ nhớ đó, điều này có thể xảy ra ở phần sau của mã. Khi điều đó xảy ra, chương trình của chúng ta hoạt động không bình thường vì vị trí bộ nhớ có giá trị sai.

  3. Bộ nhớ chưa được khởi tạo
    Vì C/C++ cho phép chúng ta tạo các biến không có giá trị ban đầu nên chúng ta có thể thử đọc dữ liệu chưa được khởi tạo. Hàm cấp phát bộ nhớ malloc[] và toán tử new không cấp phát bộ nhớ.

  4. Quản lý bộ nhớ không đúng
    Điều này có thể xảy ra khi chúng ta gọi free[] nhiều lần, truy cập bộ nhớ sau khi giải phóng nó,
    hoặc giải phóng một khối bộ nhớ .

    #include 
    #include 
    #include 
    
    void populate[char **str] {
    	// 1. OK
    	*str = [char *]malloc[sizeof[char] * 7];
    	strcpy[*str, "Memory"];
    
    	// 2. Not OK if later freeing the memory
    	*str = "Memory";
    }
    
    int main[] {
    	char *s;
    	populate[&s;];
    	printf["%s", s];   // should print "Memory"
    	free[s];
    	return 0;
    }
    
    
    Điều này cũng có thể xảy ra khi chúng ta sử dụng xóa thay vì xóa [] hoặc khi chúng ta quản lý bộ nhớ với sự kết hợp sai chức năng bộ nhớ. malloc[] với xóa hoặc mới với free[]

Toán tử mới và Toán tử mới

Sự khác biệt giữa toán tử mới và toán tử mới là gì?

Hãy nhìn vào dòng mã sau đây,

	string *pStr = new string["Where is my place in Memory?"];

cái mới là toán tử mới. Vì toán tử này được tích hợp sẵn trong C++ nên chúng ta không thể thay đổi hành vi của toán tử. Những gì nó làm là gấp đôi

  1. Nó phân bổ đủ bộ nhớ để giữ một đối tượng thuộc loại được yêu cầu. Trong ví dụ trên, nó phân bổ đủ bộ nhớ để chứa một đối tượng chuỗi
  2. Nó gọi một hàm tạo để khởi tạo một đối tượng trong bộ nhớ đã được cấp phát

Trong C++, cấp phát bộ nhớ và xây dựng đối tượng gắn bó chặt chẽ với nhau. Khi chúng ta sử dụng một bộ nhớ mới, bộ nhớ được cấp phát và một đối tượng được xây dựng trong bộ nhớ đó. Nói cách khác, new toán tử luôn làm hai việc đó và chúng ta không thể thay đổi ý nghĩa của nó cả

Khi chúng tôi tiếp quản cấp phát bộ nhớ, chúng tôi phải xử lý hai nhiệm vụ đó [cấp phát và xây dựng]. Những gì chúng ta có thể thay đổi là cách phân bổ bộ nhớ cho một đối tượng. Toán tử mới gọi một hàm để thực hiện cấp phát bộ nhớ cần thiết và chúng ta có thể viết lại hoặc nạp chồng hàm để thay đổi những gì nó đang làm. [C++ hiệu quả hơn, Mục #8 Hiểu ý nghĩa khác nhau của new và delete, Scott Meyers]

Vậy hàm new đang gọi là gì?
Đó là hàm mới.

	void * operator new [size_t size];

Kiểu trả về là void*. Vì hàm này trả về một con trỏ tới raw không được nhập và bộ nhớ chưa được khởi tạo đủ lớn để chứa một đối tượng thuộc loại đã chỉ định. size_t chỉ định dung lượng bộ nhớ cần phân bổ

Rất hiếm nhưng có khả năng chúng tôi muốn gọi trực tiếp cho tổng đài mới

________số 8

Toán tử new trả về một con trỏ tới một đoạn bộ nhớ đủ để tạo lỗ cho một đối tượng chuỗi.
Toán tử new tương tự như malloc ở chỗ nó chỉ chịu trách nhiệm cấp phát bộ nhớ. Nó không biết gì về các nhà xây dựng. Tất cả các nhà điều hành mới hiểu là cấp phát bộ nhớ. Đó là nó.

Công việc của toán tử mới là lấy bộ nhớ thô mà toán tử mới trả về và biến nó thành một đối tượng

Hãy xem xét quá trình cấp phát và khởi tạo bộ nhớ từ góc nhìn của trình biên dịch. Khi trình biên dịch nhìn thấy dòng sau,

	string *pStr = new string["Where is my place in Memory?"];

trình biên dịch tạo mã giống như thế này

________số 8

Nó lấy bộ nhớ thô cho một đối tượng chuỗi

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}
1

Sau đó, nó khởi tạo đối tượng trong bộ nhớ bằng cách gọi hàm tạo

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}
2

Dòng mã trên làm cho pString trỏ đến đối tượng mới

Khi chúng ta sử dụng biểu thức xóa để xóa một đối tượng được cấp phát động

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}
3

hai điều xảy ra. Đầu tiên, hàm hủy thích hợp được chạy trên đối tượng mà ptr trỏ tới. Sau đó, bộ nhớ được sử dụng bởi đối tượng được giải phóng bằng cách gọi hàm xóa toán tử

Không giống như các hàm toán tử khác, chẳng hạn như toán tử =, các hàm toán tử mới và toán tử xóa không làm quá tải hàm mới hoặc xóa

Các phiên bản quá tải của toán tử mới và toán tử xóa

Lưu ý rằng toán tử mới và toán tử xóa chỉ áp dụng cho phân bổ cho các đối tượng đơn lẻ. Bộ nhớ cho mảng được cấp phát bởi toán tử new[] và được giải phóng bởi toán tử delete[]. Cũng lưu ý rằng bộ nhớ heap cho bộ chứa STL được quản lý bởi các đối tượng cấp phát của bộ chứa, không phải bởi mới và xóa trực tiếp

Có hai phiên bản quá tải của hàm toán tử mới và toán tử xóa

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}
4

Khi chúng ta sử dụng new để tạo một đối tượng động, có hai điều xảy ra như chúng ta đã thảo luận trong phần trước. Đầu tiên, bộ nhớ được cấp phát bằng cách gọi toán tử mới. Thứ hai, một hoặc nhiều hàm tạo được gọi cho bộ nhớ đó

Những điều tương tự cũng xảy ra khi chúng tôi sử dụng xóa. một hoặc nhiều hàm hủy được gọi cho bộ nhớ, sau đó bộ nhớ được giải phóng bằng toán tử xóa

Câu hỏi để xóa là có bao nhiêu đối tượng nằm trong bộ nhớ bị xóa?

Vì vậy, chúng ta nên khớp cái mới và xóa. Ví dụ sau minh họa ý nghĩa của nó

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}
5

Trong trường hợp tạo mảng, toán tử new hoạt động hơi khác so với trường hợp tạo một đối tượng. Bộ nhớ không còn được cấp phát bởi toán tử mới. Thay vào đó, nó được cấp phát bởi toán tử new[]

Hãy xem quá trình tạo và xóa các đối tượng mảng

Đối với mảng, phải gọi hàm tạo cho từng đối tượng trong mảng

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}
6

Đoạn mã gọi toán tử new[] để cấp phát bộ nhớ cho đối tượng 10 chuỗi, sau đó gọi hàm tạo chuỗi mặc định cho từng phần tử mảng

Theo cách này, khi toán tử xóa được sử dụng trên một mảng, nó gọi hàm hủy cho từng phần tử mảng và sau đó gọi toán tử xóa [] để giải phóng bộ nhớ. Nó gọi hàm hủy chuỗi cho từng phần tử mảng, sau đó gọi toán tử delete[] để giải phóng bộ nhớ của mảng

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}
7

Chúng tôi có hai hình thức xóa

  1. xóa ptr - giải phóng bộ nhớ cho một đối tượng riêng lẻ được cấp phát bởi new
  2. xóa ptr[] - giải phóng bộ nhớ cho một mảng đối tượng được phân bổ bởi new

Ví dụ sau đây cho thấy cách sử dụng xóa và xóa []. Khi chúng tôi muốn xóa con trỏ tới MyClass, chúng tôi đã sử dụng xóa và trong hàm hủy được kích hoạt bởi lệnh xóa myObj sẽ xóa mảng được tạo trên heap

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}
8

Nếu chúng tôi không sử dụng phiên bản xóa mảng, chương trình của chúng tôi có thể hoạt động theo những cách kỳ lạ. Trong một số trình biên dịch, chỉ hàm hủy cho phần tử thứ 0 của mảng mới được gọi vì trình biên dịch chỉ biết rằng bạn đang xóa một con trỏ tới một đối tượng. Ở những người khác, lỗi bộ nhớ có thể xảy ra vì new và new[] có thể sử dụng các sơ đồ cấp phát bộ nhớ hoàn toàn khác nhau

Các hàm hủy chỉ được gọi nếu các phần tử của mảng là các đối tượng đơn giản, tuy nhiên, nếu chúng ta có một mảng các con trỏ, chúng ta vẫn cần xóa từng phần tử riêng lẻ giống như bạn đã phân bổ từng phần tử riêng lẻ, như được hiển thị trong đoạn mã sau

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp]; 
}

int main[]
{
	int* ptr = local_pointer[];
	return 0;
}
9

Khởi tạo các đối tượng được cấp phát động

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp;]; 
}

void f[] { int array[10000] = {1};}

int main[]
{
	int* ptr = local_pointer[];
	f[];
	return 0;
}
0

Dấu ngoặc đơn trống báo hiệu rằng chúng tôi muốn khởi tạo mà không cung cấp giá trị ban đầu cụ thể. Không giống như các kiểu dựng sẵn, trong trường hợp các kiểu lớp định nghĩa các hàm tạo của riêng chúng, yêu cầu khởi tạo không có bất kỳ hậu quả nào vì đối tượng sẽ được khởi tạo bởi hàm tạo mặc định nếu chúng ta để nó không được khởi tạo hoặc yêu cầu khởi tạo một cách rõ ràng

C không cung cấp toán tử mới và xóa. Để sử dụng, chúng ta nên sử dụng các chức năng xử lý bộ nhớ. Các hàm này được định nghĩa trong

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp;]; 
}

void f[] { int array[10000] = {1};}

int main[]
{
	int* ptr = local_pointer[];
	f[];
	return 0;
}
1

Typedef sizt_t là một loại không dấu

Tại sao malloc[] lại trả về khoảng trống*?
Đó là vì malloc[] không biết loại đối tượng nào chúng ta muốn đặt vào bộ nhớ đó. Khởi tạo là trách nhiệm của chúng tôi. Ví dụ.

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp;]; 
}

void f[] { int array[10000] = {1};}

int main[]
{
	int* ptr = local_pointer[];
	f[];
	return 0;
}
2

Lưu ý rằng chúng tôi không thể viết bằng C hoặc C++

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp;]; 
}

void f[] { int array[10000] = {1};}

int main[]
{
	int* ptr = local_pointer[];
	f[];
	return 0;
}
3

Nhưng trong C++, sau khi định nghĩa một constructor, chúng ta có thể viết

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp;]; 
}

void f[] { int array[10000] = {1};}

int main[]
{
	int* ptr = local_pointer[];
	f[];
	return 0;
}
4

Quản lý bộ nhớ trong C - malloc[], calloc[] và realloc[]


  1. Trình biên dịch C bố trí bộ nhớ tương ứng với các hàm [đối số, biến] trên ngăn xếp
  2. C cho phép lập trình viên phân bổ bộ nhớ bổ sung trên heap

Bộ nhớ Stack Heap được phân bổ khi nhập chức năng với mallocmemory được giải quyết khi chức năng trở lại với các địa chỉ miễn phí được gán tĩnh một cách linh hoạt
  1. cấp phát
    malloc là một hàm thư viện chuẩn C tìm một đoạn bộ nhớ trống có kích thước mong muốn và trả về một con trỏ tới nó.
    int* local_pointer[] 
    {
    	int temp = 100;
    	// returns a pointer to the local int
    	return[&temp;]; 
    }
    
    void f[] { int array[10000] = {1};}
    
    int main[]
    {
    	int* ptr = local_pointer[];
    	f[];
    	return 0;
    }
    
    5
  2. deallocation
    free đánh dấu bộ nhớ được liên kết với một địa chỉ cụ thể là không còn được sử dụng. [Nó theo dõi dung lượng bộ nhớ được liên kết với địa chỉ đó]
  3. int* local_pointer[] 
    {
    	int temp = 100;
    	// returns a pointer to the local int
    	return[&temp;]; 
    }
    
    void f[] { int array[10000] = {1};}
    
    int main[]
    {
    	int* ptr = local_pointer[];
    	f[];
    	return 0;
    }
    
    6

Bộ nhớ có thể được cấp phát linh hoạt bằng hàm malloc[] và có thể giải phóng bộ nhớ bằng hàm free[] khi không còn cần thiết

Hàm malloc[] yêu cầu một giá trị số nguyên cho đối số của nó để chỉ định các byte của bộ nhớ và nó trả về một con trỏ [void *]. đến không gian được phân bổ. free[] lấy một con trỏ tới không gian cho đối số của nó. Đây là một ví dụ đơn giản

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp;]; 
}

void f[] { int array[10000] = {1};}

int main[]
{
	int* ptr = local_pointer[];
	f[];
	return 0;
}
7

Nếu malloc[] không thể nhận được không gian được yêu cầu, nó sẽ trả về NULL. Trong ví dụ này, sizeof[] được sử dụng để tính tổng số byte cần dự trữ vì số byte được sử dụng để lưu trữ khác nhau tùy theo hệ thống

Có một chức năng khác rất giống với hàm malloc[]. Nó là calloc[]

  1. Hàm calloc[] nhận hai đối số nguyên. Chúng được nhân với nhau để chỉ định dung lượng bộ nhớ cần phân bổ
  2. Calloc[] khởi tạo tất cả không gian bộ nhớ được phân bổ thành 0 trong khi malloc[] để lại bất kỳ giá trị nào có thể đã có ở đó
int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp;]; 
}

void f[] { int array[10000] = {1};}

int main[]
{
	int* ptr = local_pointer[];
	f[];
	return 0;
}
8

Đầu ra là;

int* local_pointer[] 
{
	int temp = 100;
	// returns a pointer to the local int
	return[&temp;]; 
}

void f[] { int array[10000] = {1};}

int main[]
{
	int* ptr = local_pointer[];
	f[];
	return 0;
}
9

Bộ nhớ đã được cấp phát với malloc[] hoặc calloc[] có thể được tăng lên bằng cách sử dụng realloc[]

int * badPointer[] {
	int i = 100;
	return &i;
}
0

realloc[] thay đổi kích thước của đối tượng được trỏ bởi ptrc/ptrm thành 10*sizeof[int]. Nội dung sẽ không thay đổi đến mức tối thiểu của kích thước cũ và mới. Nếu kích thước mới lớn hơn, không gian mới chưa được khởi tạo. realloc[] trả về một con trỏ tới không gian mới hoặc NULL nếu yêu cầu không được đáp ứng, trong trường hợp đó ptrc/ptrm không thay đổi

Đầu ra là

int * badPointer[] {
	int i = 100;
	return &i;
}
1

void free[void* heapBlockPulum].
Hàm free[] lấy một con trỏ tới một khối heap và trả nó về nhóm miễn phí để sử dụng lại sau này. Con trỏ được chuyển tới free[] phải chính xác là con trỏ được trả về trước đó bởi malloc[], không chỉ là con trỏ tới một nơi nào đó trong khối.

Gọi free[] với loại con trỏ sai nổi tiếng với loại sự cố đặc biệt xấu xí mà nó gây ra. Lệnh gọi free[] không cần cung cấp kích thước của khối heap - trình quản lý heap sẽ lưu ý kích thước trong cấu trúc dữ liệu riêng tư của nó. Cuộc gọi đến free[] chỉ cần xác định khối nào cần phân bổ theo con trỏ của nó. Nếu một chương trình phân bổ chính xác tất cả bộ nhớ mà nó phân bổ, thì mọi lệnh gọi tới malloc[] sau đó sẽ được khớp với chính xác một lệnh gọi tới free[]

Malloc[] vs new - Tạo và xây dựng đối tượng

Ưu điểm chính của new so với malloc[] là new không chỉ cấp phát bộ nhớ, nó còn xây dựng các đối tượng

int * badPointer[] {
	int i = 100;
	return &i;
}
2

Sau khi chạy, cả objMalloc và objNew sẽ trỏ đến các vùng bộ nhớ trong heap đủ lớn cho một đối tượng Foo. Các thành viên dữ liệu và phương thức của Foo có thể được truy cập bằng cả hai con trỏ. Điểm khác biệt là đối tượng Foo được trỏ tới bởi objMalloc không phải là đối tượng phù hợp vì nó chưa bao giờ được xây dựng. Hàm malloc[] chỉ dành một phần bộ nhớ có kích thước nhất định. Nó không biết hoặc quan tâm đến các đối tượng. Ngược lại, lệnh gọi new sẽ phân bổ kích thước bộ nhớ phù hợp và cũng sẽ xây dựng đối tượng đúng cách.

Một sự khác biệt tương tự tồn tại giữa hàm free[] và hàm xóa. Với free[], hàm hủy của đối tượng sẽ không được gọi. Với xóa, hàm hủy sẽ được gọi và đối tượng sẽ được dọn sạch đúng cách

Toán tử hàm mới cấp phát nhưng không khởi tạo bộ nhớ. Toán tử mới có trách nhiệm tìm trong heap một khối bộ nhớ đủ lớn để chứa lượng bộ nhớ mà chúng tôi yêu cầu. Là một biến thể của toán tử mới, vị trí mới cho phép chúng tôi chỉ định vị trí sẽ được sử dụng. Nói cách khác, nó cho phép chúng ta xây dựng một đối tượng tại một địa chỉ bộ nhớ được cấp phát trước cụ thể. Hình thức của một vị trí mới là

int * badPointer[] {
	int i = 100;
	return &i;
}
3

trong đó địa điểm phải là một con trỏ và danh sách khởi tạo cung cấp danh sách khởi tạo để sử dụng khi xây dựng đối tượng mới được cấp phát

Để sử dụng vị trí mới, chúng tôi nên bao gồm tệp tiêu đề mới, tệp này cung cấp nguyên mẫu cho phiên bản mới này. Sau đó, chúng tôi sử dụng new với một đối số cung cấp địa chỉ dự định

int * badPointer[] {
	int i = 100;
	return &i;
}
4

Vị trí mới chỉ cần sử dụng địa chỉ được chuyển đến vị trí đó. Nó không theo dõi xem vị trí đó đã được sử dụng chưa và nó không tìm kiếm bộ nhớ chưa sử dụng trong khối. Điều này chuyển gánh nặng quản lý bộ nhớ cho lập trình viên

Đối với mới thông thường, các báo cáo

int * badPointer[] {
	int i = 100;
	return &i;
}
5

giải phóng khối bộ nhớ. Tuy nhiên, như chúng ta đã thấy trong ví dụ, chúng ta không sử dụng xóa để giải phóng bộ nhớ được sử dụng bởi vị trí mới. Trên thực tế, nó không thể. Bộ nhớ được chỉ định bởi buf là bộ nhớ tĩnh và xóa chỉ có thể được sử dụng cho một con trỏ tới bộ nhớ heap được cấp phát bởi bộ nhớ mới thông thường

Để xem vấn đề quản lý bộ nhớ của ví dụ trước, ở đây, một phiên bản được sửa đổi một chút với hàm tạo sử dụng new để tạo con trỏ tới mảng ký tự và với hàm hủy giải phóng bộ nhớ bị chiếm bởi mảng ký tự

int * badPointer[] {
	int i = 100;
	return &i;
}
6

Đầu ra là

int * badPointer[] {
	int i = 100;
	return &i;
}
7

Lưu ý rằng hàm hủy được gọi tại

int * badPointer[] {
	int i = 100;
	return &i;
}
8

Nhưng chúng ta cần gọi một hàm hủy khác cho đối tượng được tạo bởi

int * badPointer[] {
	int i = 100;
	return &i;
}
9

Làm thế nào chúng ta có thể làm điều đó?

Đây là giải pháp. thêm dòng sau vào cuối main[]

int * pointerToStatic[] {
	static i;
	return &i;
}
0

Sau đó, đầu ra trở thành

int * pointerToStatic[] {
	static i;
	return &i;
}
1

Chúng tôi gọi hàm hủy một cách rõ ràng cho bất kỳ đối tượng nào được tạo bởi vị trí mới. Thông thường, hàm hủy được gọi tự động và đây là một trong những trường hợp hiếm hoi yêu cầu gọi rõ ràng. Một cuộc gọi rõ ràng đến một hàm hủy yêu cầu xác định đối tượng sẽ bị hủy

Các lỗi phổ biến đối với phân bổ bộ nhớ

Hầu hết các lỗi C++ phát sinh từ một số kiểu lạm dụng con trỏ và tham chiếu

  1. Hủy bỏ hội thảo
    Cố gắng sử dụng toán tử -> hoặc * trên con trỏ NULL.

  2. Giải phóng kép
    Gọi xóa hoặc giải phóng [] trên một khối bộ nhớ hai lần.

  3. Đang truy cập bộ nhớ không hợp lệ
    Đang thử sử dụng toán tử -> hoặc * trên một con trỏ chưa được cấp phát hoặc đã được giải phóng.

  4. Kết hợp các trình cấp phát
    Sử dụng xóa để giải phóng bộ nhớ được cấp phát bằng malloc[] hoặc sử dụng free[] để trả về bộ nhớ được cấp phát mới.

  5. Xử lý mảng không chính xác
    Sử dụng toán tử xóa thay vì xóa[] để giải phóng một mảng.

  6. Rò rỉ bộ nhớ
    Không giải phóng một khối bộ nhớ khi chúng tôi xử lý xong.

Những vấn đề này phát sinh vì khó có thể biết liệu một con trỏ C++ đang tham chiếu đến bộ nhớ hợp lệ hay nó đang trỏ đến bộ nhớ chưa được phân bổ hoặc giải phóng. Nhưng chúng ta có thể tránh những vấn đề này bằng cách sử dụng con trỏ được quản lý [con trỏ thông minh]. Những con trỏ này không phải là một phần của đặc tả C++ 98 ban đầu. Nhưng chúng đã được đưa vào TR1 [Báo cáo kỹ thuật 1]. Chúng cũng được bao gồm trong C++0X. Các thư viện Boost cung cấp

triển khai mã nguồn mở, di động của các con trỏ này. tăng. shared_ptr, tăng. yếu_ptr, tăng cường. phạm vi_ptr
  1. Con trỏ dùng chung
    Con trỏ dùng chung là con trỏ được đếm tham chiếu trong đó số tham chiếu tăng lên một khi một đoạn mã muốn giữ con trỏ và giảm đi một khi sử dụng xong . Khi số tham chiếu bằng 0, đối tượng được con trỏ trỏ tới sẽ tự động được giải phóng. Vì vậy, các con trỏ được chia sẻ có thể giúp tránh các sự cố khi truy cập bộ nhớ đã giải phóng bằng cách đảm bảo rằng con trỏ vẫn hợp lệ trong khoảng thời gian mà chúng tôi muốn sử dụng nó.

  2. Con trỏ yếu
    Con trỏ yếu chứa con trỏ tới một đối tượng, thông thường là con trỏ dùng chung, nhưng nó không đóng góp vào số lượng tham chiếu cho đối tượng đó. Nếu chúng ta có một con trỏ dùng chung và một con trỏ yếu tham chiếu cùng một đối tượng và con trỏ dùng chung bị hủy, con trỏ yếu ngay lập tức trở thành NULL. Vì vậy, con trỏ yếu có thể phát hiện xem đối tượng được trỏ tới đã hết hạn hay chưa nếu số tham chiếu cho đối tượng mà nó trỏ tới bằng 0. Điều này giúp tránh vấn đề con trỏ lơ lửng nơi chúng ta có thể có một con trỏ đang tham chiếu đến bộ nhớ được giải phóng.

  3. Con trỏ có phạm vi
    Con trỏ có phạm vi hỗ trợ quyền sở hữu đối tượng đơn lẻ và tự động phân bổ đối tượng của chúng khi con trỏ vượt ra ngoài phạm vi. Vì vậy, đôi khi chúng được gọi là con trỏ tự động [so sánh điều này với auto_ptr]. Con trỏ phạm vi được định nghĩa là sở hữu một đối tượng duy nhất, vì vậy nó không thể được sao chép.

Hãy xem ví dụ sau

int * pointerToStatic[] {
	static i;
	return &i;
}
2

Trong ví dụ này, hai phiên bản của MyClass được tạo và cả hai phiên bản này đều bị hủy khi ptr vượt quá phạm vi. Thay vào đó, nếu phương thức createInstance[] chỉ trả về kiểu MyClass *, thì hàm hủy sẽ không bao giờ được gọi trong ví dụ này. Do đó, việc sử dụng con trỏ thông minh có thể giúp việc quản lý bộ nhớ trở nên đơn giản hơn

Nói chung, nếu chúng ta có một hàm trả về một con trỏ mà máy khách của chúng ta nên xóa hoặc nếu chúng ta muốn máy khách cần con trỏ lâu hơn tuổi thọ của đối tượng, thì chúng ta nên trả về nó bằng con trỏ samrt. Tuy nhiên, nếu quyền sở hữu con trỏ sẽ được giữ lại bởi đối tượng của chúng ta, thì chúng ta có thể trả về một con trỏ chuẩn như bên dưới

int * pointerToStatic[] {
	static i;
	return &i;
}
3

thay vì

int * pointerToStatic[] {
	static i;
	return &i;
}
4

Kiểm tra thêm, Gỡ lỗi Sự cố & Rò rỉ Bộ nhớ

Trong phần này, chúng ta sẽ nói về bộ nhớ giới hạn cho hệ thống Linux mặc dù nó cũng có thể được áp dụng cho các hệ thống khác.
Bộ nhớ được cấp phát của ứng dụng được quản lý bởi nhân Linux. Bất cứ khi nào chương trình yêu cầu bộ nhớ hoặc cố gắng đọc hoặc ghi vào bộ nhớ đã được cấp phát, nhân Linux sẽ chịu trách nhiệm và quyết định cách xử lý yêu cầu đó.

Ban đầu, hạt nhân chỉ đơn giản là có thể sử dụng bộ nhớ vật lý trống để đáp ứng yêu cầu về bộ nhớ của ứng dụng, tuy nhiên, khi bộ nhớ vật lý đầy, nó bắt đầu sử dụng không gian hoán đổi. Đây là một vùng đĩa riêng biệt được phân bổ khi hệ thống được định cấu hình và nhân Linux thực hiện tất cả việc quản lý cho chúng tôi. Nói cách khác, phần đĩa cứng được sử dụng làm bộ nhớ ảo được gọi là không gian hoán đổi

Nhân di chuyển dữ liệu và mã chương trình giữa bộ nhớ vật lý và không gian hoán đổi sao cho mỗi lần chúng ta đọc/ghi bộ nhớ, dữ liệu luôn có vẻ như đã ở trong bộ nhớ vật lý, bất kể nó thực sự nằm ở đâu trước khi chúng ta cố gắng truy cập.

Vùng đĩa cứng lưu trữ hình ảnh RAM được gọi là tệp trang. Nó chứa các trang RAM trên đĩa cứng và hệ điều hành di chuyển dữ liệu qua lại giữa tệp trang và RAM. Trên máy Windows, các tệp trang có một. tiện ích mở rộng SWP

Trên thực tế, Linux triển khai hệ thống bộ nhớ ảo được phân trang theo yêu cầu. Tất cả bộ nhớ mà chương trình người dùng nhìn thấy là ảo. Nói cách khác, nó không tồn tại ở địa chỉ vật lý mà chương trình sử dụng. Linux chia tất cả bộ nhớ thành các trang, thông thường là 4.096 byte trên mỗi trang. Khi một chương trình cố gắng truy cập bộ nhớ, một bản dịch ảo sang vật lý được thực hiện. Khi quyền truy cập vào bộ nhớ không nằm trong vật lý, sẽ xảy ra lỗi trang và quyền kiểm soát được chuyển đến nhân

Một số phần bộ nhớ ảo có thể được ánh xạ tới trang không có bộ nhớ vật lý. Vì vậy, khi một quá trình cố gắng truy cập vào một ô nhớ trong một phần như vậy, CPU sẽ xác định lỗi trang và gọi một thường trình HĐH cần xử lý lỗi này. Hầu hết các HĐH sử dụng tính năng này để lưu trữ một phần của các phần bộ nhớ ảo trên đĩa thay vì trong RAM, do đó cho phép chương trình sử dụng không gian địa chỉ lớn hơn RAM vật lý của máy. Khi xảy ra lỗi trang, HĐH sẽ tải nội dung của phần bộ nhớ ảo bị lỗi từ đĩa, sao chép nó vào một trang bộ nhớ vật lý trống và cập nhật bảng dịch bộ nhớ ảo để CPU biết ánh xạ phần bộ nhớ ảo tới

Trong Linux, kernel kiểm tra địa chỉ đang được truy cập và nếu đó là địa chỉ hợp pháp cho chương trình đó, nó sẽ xác định trang nào của bộ nhớ vật lý sẽ khả dụng. Sau đó, nó cấp phát nó, nếu nó chưa từng được ghi trước đó, hoặc, nếu nó đã được lưu trữ trên đĩa trong không gian hoán đổi, đọc trang bộ nhớ chứa dữ liệu vào bộ nhớ vật lý, có thể di chuyển một trang hiện có ra đĩa. Sau đó, sau khi ánh xạ địa chỉ bộ nhớ ảo khớp với địa chỉ vật lý, nó cho phép chương trình người dùng tiếp tục. Các ứng dụng Linux không cần phải lo lắng về hoạt động này vì tất cả việc triển khai đều được ẩn trong kernel

Tuy nhiên, nếu ứng dụng làm cạn kiệt cả bộ nhớ vật lý và không gian hoán đổi hoặc khi vượt quá kích thước ngăn xếp tối đa, hạt nhân cuối cùng sẽ từ chối yêu cầu thêm bộ nhớ và có thể chấm dứt chương trình trước.

Bộ nhớ ảo & Bảo vệ bộ nhớ

HĐH sử dụng bảng dịch để ánh xạ bộ nhớ ảo sang bộ nhớ vật lý và hệ thống có thể sử dụng bảng dịch khác nhau cho từng quy trình, do đó cung cấp cho mỗi quy trình không gian địa chỉ riêng.


Hình ảnh từ http. // vi. wikipedia. org/wiki/Address_space

Điều này có nghĩa là cùng một địa chỉ bộ nhớ ảo được sử dụng bởi hai quy trình khác nhau, sẽ được ánh xạ thành hai địa chỉ bộ nhớ vật lý khác nhau. Nói cách khác, một tiến trình không thể truy cập nội dung của bộ nhớ được sử dụng bởi tiến trình kia, và do đó, một tiến trình làm hỏng bộ nhớ của nó sẽ không can thiệp vào nội dung của bộ nhớ của bất kỳ tiến trình nào khác trong hệ thống. Tính năng này được gọi là bảo vệ bộ nhớ

Cách triển khai thu gom rác trong C++

Từ Cách triển khai thu gom rác trong C++

Thu gom rác trong C và C++ đều là những chủ đề khó vì một vài lý do

  1. Con trỏ có thể được đánh máy thành số nguyên và ngược lại. Điều này có nghĩa là tôi có thể có một khối bộ nhớ chỉ có thể truy cập được bằng cách lấy một số nguyên, nhập nó vào một con trỏ, sau đó hủy bỏ hội nghị của nó. Người thu gom rác phải cẩn thận để không nghĩ rằng một khối là không thể truy cập được khi thực sự nó vẫn có thể đạt được
  2. Con trỏ không mờ. Nhiều trình thu gom rác, chẳng hạn như trình thu gom dừng và sao chép, thích di chuyển các khối bộ nhớ xung quanh hoặc thu gọn chúng để tiết kiệm dung lượng. Vì bạn có thể xem xét rõ ràng các giá trị con trỏ trong C và C++, điều này có thể khó thực hiện chính xác. Bạn sẽ phải chắc chắn rằng nếu ai đó đang làm điều gì đó phức tạp với việc đánh máy thành số nguyên thì bạn đã cập nhật chính xác số nguyên đó nếu bạn di chuyển một khối bộ nhớ xung quanh
  3. Quản lý bộ nhớ có thể được thực hiện rõ ràng. Bất kỳ trình thu gom rác nào cũng cần tính đến việc người dùng có thể giải phóng rõ ràng các khối bộ nhớ bất kỳ lúc nào
  4. Trong C++, có sự tách biệt giữa cấp phát/thu xếp và xây dựng/hủy đối tượng. Một khối bộ nhớ có thể được phân bổ với đủ không gian để chứa một đối tượng mà không cần bất kỳ đối tượng nào thực sự được xây dựng ở đó. Một trình thu gom rác tốt sẽ cần biết, khi nó lấy lại bộ nhớ, có hay không gọi hàm hủy cho bất kỳ đối tượng nào có thể được phân bổ ở đó. Điều này đặc biệt đúng đối với các bộ chứa thư viện tiêu chuẩn, thường sử dụng std. người cấp phát sử dụng thủ thuật này vì lý do hiệu quả
  5. Bộ nhớ có thể được phân bổ từ các khu vực khác nhau. C và C++ có thể lấy bộ nhớ từ kho lưu trữ miễn phí tích hợp sẵn [malloc/free hoặc new/delete] hoặc từ HĐH thông qua mmap hoặc các lời gọi hệ thống khác, và trong trường hợp của C++, từ get_temporary_buffer hoặc return_temporary_buffer. Các chương trình cũng có thể lấy bộ nhớ từ một số thư viện của bên thứ ba. Một trình thu gom rác tốt cần có khả năng theo dõi các tham chiếu đến bộ nhớ trong các nhóm khác này và [có thể] sẽ phải chịu trách nhiệm dọn sạch chúng
  6. Con trỏ có thể trỏ vào giữa đối tượng hoặc mảng. Trong nhiều ngôn ngữ thu gom rác như Java, các tham chiếu đối tượng luôn trỏ đến phần đầu của đối tượng. Trong C và C++, các con trỏ có thể trỏ vào giữa các mảng và trong C++ vào giữa các đối tượng [nếu sử dụng đa kế thừa]. Điều này có thể làm phức tạp đáng kể logic để phát hiện những gì vẫn có thể truy cập được

Vì vậy, tóm lại, rất khó để xây dựng trình thu gom rác cho C hoặc C++. Hầu hết các thư viện thực hiện thu gom rác trong C và C++ đều cực kỳ bảo thủ trong cách tiếp cận của họ và không có cơ sở kỹ thuật - họ cho rằng bạn sẽ không, chẳng hạn, lấy một con trỏ, chuyển nó thành một số nguyên, ghi nó vào đĩa, rồi tải . Họ cũng cho rằng bất kỳ giá trị nào trong bộ nhớ có kích thước bằng một con trỏ đều có thể là một con trỏ và do đó, đôi khi từ chối giải phóng bộ nhớ không thể truy cập vì có khả năng khác không là có một con trỏ tới nó

Như những người khác đã chỉ ra, Boehm GC thực hiện thu gom rác cho C và C++, nhưng tuân theo các hạn chế đã nói ở trên

Thật thú vị, tiêu chuẩn C++ 0x sắp tới sẽ bao gồm một số chức năng thư viện mới cho phép lập trình viên đánh dấu các vùng bộ nhớ là có thể truy cập và không thể truy cập để dự đoán các nỗ lực thu gom rác trong tương lai. Trong tương lai có thể xây dựng một trình thu gom rác C++ thực sự tốt với loại thông tin này. Tuy nhiên, trong thời gian chờ đợi, bạn cần cực kỳ cẩn thận để không vi phạm bất kỳ quy tắc nào ở trên

PHP có bỏ đặt bộ nhớ trống không?

unset[] thực hiện đúng như tên gọi của nó - bỏ đặt biến. Nó không buộc giải phóng bộ nhớ ngay lập tức . Trình thu gom rác của PHP sẽ làm điều đó khi nó thấy phù hợp - theo chủ ý ngay lập tức, vì các chu kỳ CPU đó dù sao cũng không cần thiết hoặc muộn nhất là trước khi tập lệnh hết bộ nhớ, bất cứ điều gì xảy ra trước.

Làm cách nào để xóa bộ nhớ trong PHP?

Nếu bạn muốn xóa bộ nhớ thì có thể dùng unset hoặc chỉ cần đưa vào hàm khác thì các biến sẽ bị xóa.

Làm cách nào để bỏ đặt đối tượng trong PHP?

Một đối tượng là một thể hiện của một lớp. Sử dụng hàm unset[] của PHP , chúng ta có thể xóa một đối tượng. Như vậy với hàm unset[] của PHP đặt đối tượng mà chúng ta muốn xóa làm tham số cho hàm này thì chúng ta có thể xóa đối tượng này.

Làm cách nào để bỏ đặt biến toàn cục trong PHP?

Hàm unset[] trong PHP đặt lại bất kỳ biến nào. Nếu unset[] được gọi bên trong một hàm do người dùng định nghĩa, nó sẽ hủy đặt các biến cục bộ. Nếu người dùng muốn hủy đặt biến toàn cục bên trong hàm thì họ phải sử dụng mảng $GLOBALS để làm như vậy. Hàm unset[] không có giá trị trả về.

Chủ Đề