2009-10-23 74 views
9

Trong C + +, giả sử không có tối ưu hóa, làm hai chương trình sau kết thúc với cùng một mã máy phân bổ bộ nhớ?Phân bổ bộ nhớ tự động thực sự hoạt động như thế nào trong C++?

int main() 
{  
    int i; 
    int *p; 
} 

int main() 
{ 
    int *p = new int; 
    delete p; 
} 
+9

Làm cách nào để phân phối bộ nhớ giống nhau? Việc đầu tiên phân bổ một kích thước cho một int (trên stack) và một con trỏ (trên stack). Cái thứ hai phân bổ một con trỏ trên ngăn xếp và không gian cho một int trên heap. – bobbyalex

+1

Về cơ bản câu hỏi của tôi là "Khi chạy, sự khác biệt giữa ngăn xếp và đống?" – Tarquila

+2

Thử ở đây: http://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap – Alex

Trả lời

17

Không, không tối ưu hóa ...

int main() 
{  
    int i; 
    int *p; 
} 

làm gần như không có gì - chỉ là một vài hướng dẫn để điều chỉnh con trỏ ngăn xếp, nhưng

int main() 
{ 
    int *p = new int; 
    delete p; 
} 

phân bổ một khối bộ nhớ trên heap và sau đó giải phóng nó, đó là một toàn bộ rất nhiều công việc (tôi nghiêm túc ở đây - phân bổ đống không phải là một hoạt động tầm thường).

+4

+1, phân bổ heap gọi rất nhiều thông tin giữ sách, chỉ để nói về chi phí bộ nhớ. –

2

Có vẻ như bạn không biết về chồng và đống. Ví dụ đầu tiên của bạn chỉ là phân bổ một số bộ nhớ trên ngăn xếp sẽ bị xóa ngay khi nó nằm ngoài phạm vi. Bộ nhớ trên heap mà thu được bằng cách sử dụng malloc/new sẽ ở lại xung quanh cho đến khi bạn xóa nó bằng cách sử dụng miễn phí/xóa.

+1

Khi nó được "xóa ngay sau khi nó đi ra khỏi phạm vi" - không trình biên dịch đưa vào mã để làm điều đó? – Tarquila

+0

Trình biên dịch xử lý ngăn xếp và khung ngăn xếp cần thiết cho quy tắc phạm vi, vì vậy theo cách có ... nhưng 'xóa' nội dung nào đó trên ngăn xếp là tầm thường vì nó thực sự chỉ liên quan đến việc giảm con trỏ ngăn xếp và không có gì giống như phân bổ đống và deallocation. – workmad3

+0

Có, nó sẽ di chuyển con trỏ ngăn xếp về phía sau. Ngăn xếp là một kích thước cố định (hoặc ít nhất bạn có thể giả vờ là). – gnud

2

Trong chương trình đầu tiên, tất cả các biến của bạn đều nằm trên ngăn xếp. Bạn không phân bổ bất kỳ bộ nhớ động nào. 'p' chỉ là ngồi trên ngăn xếp và nếu bạn dereference nó, bạn sẽ nhận được rác thải. Trong chương trình thứ hai, bạn đang thực sự tạo ra một giá trị số nguyên trên heap. 'p' thực sự trỏ đến một số bộ nhớ hợp lệ trong trường hợp này. Bạn thực sự có thể dereference p và đặt nó vào một cái gì đó có ý nghĩa một cách an toàn:

*p = 5; 

Đó là hợp lệ trong chương trình thứ hai (trước khi xóa), không phải là đầu tiên. Hy vọng rằng sẽ giúp.

6
int i; 
    int *p; 

^Phân bổ một số nguyên và một con trỏ số nguyên trên stack

int *p = new int; 
delete p; 

^Phân bổ một con trỏ số nguyên trên stack và khối kích thước của số nguyên trên đống

EDIT:

Sự khác biệt giữa phân đoạn Ngăn xếp và đoạn Heap

alt text http://www.maxi-pedia.com/web_files/images/HeapAndStack.png

void another_function(){ 
    int var1_in_other_function; /* Stack- main-y-sr-another_function-var1_in_other_function */ 
    int var2_in_other_function;/* Stack- main-y-sr-another_function-var1_in_other_function-var2_in_other_function */ 
} 
int main() {      /* Stack- main */ 
    int y;      /* Stack- main-y */ 
    char str;      /* Stack- main-y-sr */ 
    another_function();   /*Stack- main-y-sr-another_function*/ 
    return 1 ;     /* Stack- main-y-sr */ //stack will be empty after this statement       
} 

Bất cứ khi nào bất kỳ chương trình bắt đầu thực hiện nó sẽ lưu tất cả các biến của nó trong đặc biệt vị trí bộ nhớ memoy gọi stack segment. Ví dụ trong trường hợp hàm C/C++ đầu tiên được gọi là chính. do đó, nó sẽ được đặt trên ngăn xếp đầu tiên. Bất kỳ biến nào bên trong chính sẽ được đặt trên stack khi chương trình thực thi. Bây giờ như chính là chức năng đầu tiên được gọi là nó sẽ là chức năng cuối cùng để trả lại bất kỳ giá trị (Hoặc sẽ được popped từ stack).

Bây giờ, khi bạn phân bổ động bộ nhớ bằng cách sử dụng new, một vị trí bộ nhớ đặc biệt khác được sử dụng được gọi là phân đoạn Heap. Ngay cả khi dữ liệu thực tế có mặt trên heap pointer nằm trên stack.

+0

Điều đó hữu ích. Trong sơ đồ của bạn, bạn đã vẽ đống bằng mũi tên - tôi cho rằng bạn có nghĩa là có thể có bất kỳ khoảng cách nào (bộ nhớ không thuộc về chương trình) giữa ngăn xếp và vùng heap? – bobobobo

+0

Mỗi quá trình được phân bổ 4GB bộ nhớ ảo, do đó bộ nhớ sẽ được phân phối trong các phân đoạn – Xinus

23

Để hiểu rõ hơn những gì đang diễn ra, hãy tưởng tượng rằng chúng tôi chỉ có một hệ điều hành rất nguyên thủy chạy trên bộ xử lý 16 bit chỉ có thể chạy một quy trình tại một thời điểm. Điều này là để nói: chỉ có một chương trình có thể chạy cùng một lúc. Hơn nữa, chúng ta hãy giả vờ rằng tất cả các ngắt bị vô hiệu hóa.

Có một cấu trúc trong bộ xử lý của chúng tôi được gọi là ngăn xếp. Ngăn xếp là một cấu trúc logic được áp đặt trên bộ nhớ vật lý. Giả sử RAM của chúng ta tồn tại ở các địa chỉ E000 đến FFFF. Điều này có nghĩa là chương trình đang chạy của chúng tôi có thể sử dụng bộ nhớ này theo bất kỳ cách nào chúng tôi muốn. Hãy tưởng tượng rằng hệ điều hành của chúng ta nói rằng E000 đến EFFF là ngăn xếp, và F000 tới FFFF là đống.

Ngăn xếp được duy trì bằng phần cứng và theo hướng dẫn của máy. Chúng ta không cần phải làm gì để duy trì nó. Tất cả chúng ta (hoặc hệ điều hành của chúng ta) cần làm là đảm bảo chúng ta thiết lập một địa chỉ thích hợp để bắt đầu ngăn xếp. Con trỏ ngăn xếp là một thực thể vật lý, nằm trong phần cứng (bộ xử lý) và được quản lý bởi các hướng dẫn của bộ xử lý. Trong trường hợp này, con trỏ ngăn xếp của chúng tôi sẽ được đặt thành EFFF (giả sử ngăn xếp tăng BACKWARDS, điều này khá phổ biến, -). Với một ngôn ngữ được biên dịch như C, khi bạn gọi một hàm, nó đẩy bất kỳ đối số nào bạn đã chuyển vào hàm trên ngăn xếp. Mỗi đối số có một kích thước nhất định. int thường là 16 hoặc 32 bit, char thường là 8 bit, v.v. Giả sử rằng trên hệ thống của chúng ta, int và int * là 16 bit. Đối với mỗi đối số, con trỏ ngăn xếp được DECREMENTED (-) bởi sizeof (đối số) và đối số được sao chép vào ngăn xếp. Sau đó, bất kỳ biến nào bạn đã khai báo trong phạm vi được đẩy lên ngăn xếp theo cùng một cách, nhưng giá trị của chúng không được khởi tạo.

Hãy xem xét lại hai ví dụ tương tự như hai ví dụ của bạn.

int hello(int eeep) 
{ 
    int i; 
    int *p; 
} 

Điều gì xảy ra ở đây trên hệ thống 16 bit của chúng tôi như sau: 1) đẩy eeep lên ngăn xếp. Điều này có nghĩa là chúng ta giảm con trỏ ngăn xếp thành EFFD (bởi vì sizeof (int) là 2) và sau đó thực sự copy eeep để giải quyết EFFE (giá trị hiện tại của con trỏ ngăn xếp của chúng ta, trừ 1 bởi vì con trỏ ngăn xếp của chúng ta trỏ đến vị trí đầu tiên có sẵn sau khi phân bổ). Đôi khi có hướng dẫn có thể làm cả hai trong một ngã swoop (giả sử bạn đang sao chép dữ liệu phù hợp trong một đăng ký. Nếu không, bạn phải tự sao chép từng yếu tố của một datatype đến vị trí thích hợp của nó trên stack - trật tự vấn đề!).

2) tạo không gian cho i. Điều này có thể có nghĩa là chỉ cần giảm con trỏ ngăn xếp thành EFFB.

3) tạo không gian cho p. Điều này có thể có nghĩa là chỉ cần giảm con trỏ ngăn xếp thành EFF9.

Sau đó chương trình của chúng tôi chạy, ghi nhớ nơi các biến của chúng tôi hoạt động (eeep bắt đầu tại EFFE, i tại EFFC và p tại EFFA). Điều quan trọng cần nhớ là mặc dù stack đếm BACKWARDS, các biến vẫn hoạt động FORWARDS (điều này thực sự phụ thuộc vào endianness, nhưng điểm là & eeep == EFFE, chứ không phải EFFF).

Khi chức năng đóng cửa, chúng tôi chỉ đơn giản là tăng (++) con trỏ ngăn xếp bởi 6, (vì 3 "đối tượng", không phải là C++ loại, kích thước 2 đã được đẩy vào stack.

Bây giờ, kịch bản thứ hai của bạn có nhiều khó khăn để giải thích vì có rất nhiều phương pháp để hoàn thành nó rằng nó hầu như không thể giải thích trên internet.

int hello(int eeep) 
{ 
    int *p = malloc(sizeof(int));//C's pseudo-equivalent of new 
    free(p);//C's pseudo-equivalent of delete 
} 

eeep và p vẫn được đẩy và cấp phát trên stack như trong trước Ví dụ, trong trường hợp này, chúng ta khởi tạo p cho kết quả của một lời gọi hàm, cái gì là malloc (hoặc mới, nhưng mới làm được nhiều hơn trong C++. tructors khi thích hợp, và tất cả mọi thứ khác.) là nó đi vào hộp đen này được gọi là HEAP và nhận được một địa chỉ của bộ nhớ miễn phí. Hệ điều hành của chúng tôi sẽ quản lý heap cho chúng ta, nhưng chúng ta phải cho nó biết khi nào chúng ta muốn bộ nhớ và khi chúng ta hoàn thành nó.

Trong ví dụ, khi gọi hàm malloc(), hệ điều hành sẽ trả về một khối 2 byte (sizeof (int) trên hệ thống của chúng tôi là 2) bằng cách cho chúng tôi địa chỉ bắt đầu của các byte này. Hãy nói rằng cuộc gọi đầu tiên đã cho chúng tôi địa chỉ F000. Hệ điều hành sau đó theo dõi địa chỉ F000 và F001 hiện đang được sử dụng. Khi chúng tôi gọi miễn phí (p), hệ điều hành sẽ tìm thấy khối bộ nhớ mà p trỏ tới và đánh dấu 2 byte là không sử dụng (vì sizeof (sao p) là 2). Nếu thay vào đó chúng ta phân bổ nhiều bộ nhớ hơn, địa chỉ F002 có thể sẽ được trả về như là khối khởi đầu của bộ nhớ mới. Lưu ý rằng chính malloc() là một hàm. Khi p được đẩy vào ngăn xếp cho cuộc gọi của malloc(), p được sao chép vào ngăn xếp một lần nữa tại địa chỉ mở đầu tiên có đủ chỗ trên ngăn xếp để vừa với kích thước của p (có lẽ là EFFB, bởi vì chúng tôi chỉ đẩy 2 những thứ trong ngăn xếp lần này kích thước 2 và sizeof (p) là 2), và con trỏ ngăn xếp được giảm lại thành EFF9 và malloc() sẽ đặt các biến cục bộ của nó trên ngăn xếp bắt đầu ở vị trí này. Khi malloc kết thúc, nó bật tất cả các mục của nó ra khỏi ngăn xếp và đặt con trỏ ngăn xếp thành thứ trước khi nó được gọi. Giá trị trả về của malloc(), một dấu sao trống, có thể sẽ được đặt trong một số thanh ghi (thường là bộ tích lũy trên nhiều hệ thống) để chúng ta sử dụng.

Khi triển khai, cả hai ví dụ THỰC SỰ không đơn giản như vậy. Khi bạn cấp phát bộ nhớ ngăn xếp, cho một cuộc gọi hàm mới, bạn phải đảm bảo rằng bạn lưu trạng thái của mình (lưu tất cả các thanh ghi) để hàm mới không xóa các giá trị vĩnh viễn. Điều này thường liên quan đến việc đẩy chúng vào ngăn xếp, quá. Trong cùng một cách, bạn thường sẽ lưu sổ đăng ký bộ đếm chương trình để bạn có thể quay lại đúng vị trí sau khi chương trình con trả về. Quản lý bộ nhớ sử dụng bộ nhớ của riêng mình để "nhớ" bộ nhớ đã được đưa ra và những gì đã không. Bộ nhớ ảo và phân đoạn bộ nhớ làm phức tạp quá trình này, và các thuật toán quản lý bộ nhớ phải liên tục di chuyển các khối xung quanh (và bảo vệ chúng) để tránh phân mảnh bộ nhớ (toàn bộ chủ đề của riêng nó), và liên kết này với bộ nhớ ảo cũng. Ví dụ thứ 2 thực sự là một loại giun lớn so với ví dụ đầu tiên. Ngoài ra, chạy nhiều quy trình làm cho tất cả điều này phức tạp hơn nhiều, vì mỗi tiến trình có ngăn xếp riêng của nó và vùng heap có thể được truy cập bởi nhiều quá trình (có nghĩa là nó phải tự bảo vệ). Ngoài ra, mỗi kiến ​​trúc bộ xử lý khác nhau. Một số kiến ​​trúc sẽ mong bạn đặt con trỏ ngăn xếp thành địa chỉ miễn phí đầu tiên trên ngăn xếp, những người khác sẽ mong bạn trỏ nó đến vị trí không miễn phí đầu tiên.

Tôi hy vọng điều này đã giúp ích. làm ơn cho tôi biết.

thông báo, tất cả các ví dụ trên đều dành cho máy hư cấu được đơn giản hóa quá mức. Trên phần cứng thực sự, điều này có nhiều lông hơn một chút.

chỉnh sửa: dấu hoa thị không hiển thị. i thay thế chúng với từ "ngôi sao"


Đối với những gì nó có giá trị, nếu chúng ta sử dụng (chủ yếu) cùng mã trong các ví dụ, thay thế "hello" với "example1" và "example2", tương ứng, chúng tôi có được đầu ra asembly sau đây cho intel trên wndows.

 
    .file "test1.c" 
    .text 
.globl _example1 
    .def _example1; .scl 2; .type 32; .endef 
_example1: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $8, %esp 
    leave 
    ret 
.globl _example2 
    .def _example2; .scl 2; .type 32; .endef 
_example2: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $8, %esp 
    movl $4, (%esp) 
    call _malloc 
    movl %eax, -4(%ebp) 
    movl -4(%ebp), %eax 
    movl %eax, (%esp) 
    call _free 
    leave 
    ret 
    .def _free; .scl 3; .type 32; .endef 
    .def _malloc; .scl 3; .type 32; .endef 
+0

như có thể được mong đợi với một cái gì đó tẻ nhạt này, tôi tiếp tục tìm lỗi nhỏ. xin vui lòng bình luận về họ và tôi sẽ cập nhật nếu tôi nghĩ rằng đó là một điểm tốt. –

+0

FF002 => F002. Mục nhập rất đẹp! Tôi nghĩ rằng bạn có thể làm cho nó rõ ràng hơn với một vài sơ đồ ascii của các bố trí bộ nhớ mà bạn nói về, nhưng đó sẽ là nhiều công việc hơn. :) – Bill

+0

đây là một wiki cộng đồng, vì vậy nếu có ai muốn thêm sơ đồ (gợi ý tốt) xin vui lòng làm như vậy. –

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