Bộ nhớ trong được phân bổ vào thời gian biên dịch thực sự có ý nghĩa gì?


159

Trong các ngôn ngữ lập trình như C và C ++, mọi người thường đề cập đến phân bổ bộ nhớ tĩnh và động. Tôi hiểu khái niệm này nhưng cụm từ "Tất cả bộ nhớ đã được phân bổ (dành riêng) trong thời gian biên dịch" luôn làm tôi bối rối.

Theo tôi hiểu, việc biên dịch, chuyển đổi mã C / C ++ mức cao sang ngôn ngữ máy và xuất ra một tệp thực thi. Bộ nhớ được "cấp phát" trong một tệp được biên dịch như thế nào? Không phải bộ nhớ luôn được phân bổ trong RAM với tất cả các công cụ quản lý bộ nhớ ảo?

Không phải là cấp phát bộ nhớ theo định nghĩa một khái niệm thời gian chạy?

Nếu tôi tạo biến 1KB được cấp phát tĩnh trong mã C / C ++ của mình, điều đó có làm tăng kích thước của tệp thực thi bằng cùng một lượng không?

Đây là một trong những trang sử dụng cụm từ dưới tiêu đề "Phân bổ tĩnh".

Quay lại vấn đề cơ bản: Phân bổ bộ nhớ, đi xuống lịch sử


mã và dữ liệu hoàn toàn tách biệt trong hầu hết các kiến ​​trúc hiện đại. trong khi các tệp nguồn chứa cả dữ liệu mã ở cùng một nơi, thùng chỉ có tham chiếu đến dữ liệu. Điều này có nghĩa là dữ liệu tĩnh trong nguồn chỉ được giải quyết dưới dạng tham chiếu.
Cholthi Paul Ttiopic

Câu trả lời:


184

Bộ nhớ được phân bổ tại thời gian biên dịch có nghĩa là trình biên dịch giải quyết tại thời gian biên dịch trong đó một số thứ nhất định sẽ được phân bổ bên trong bản đồ bộ nhớ quy trình.

Ví dụ, hãy xem xét một mảng toàn cầu:

int array[100];

Trình biên dịch biết tại thời gian biên dịch kích thước của mảng và kích thước của một int, vì vậy nó biết toàn bộ kích thước của mảng tại thời gian biên dịch. Ngoài ra, một biến toàn cục có thời lượng lưu trữ tĩnh theo mặc định: nó được phân bổ trong vùng bộ nhớ tĩnh của không gian bộ nhớ quá trình (phần .data / .bss). Với thông tin đó, trình biên dịch quyết định trong quá trình biên dịch trong địa chỉ của vùng bộ nhớ tĩnh mà mảng sẽ có .

Tất nhiên địa chỉ bộ nhớ là địa chỉ ảo. Chương trình giả định rằng nó có toàn bộ không gian bộ nhớ (ví dụ: từ 0x00000000 đến 0xFFFFFFFF). Đó là lý do tại sao trình biên dịch có thể thực hiện các giả định như "Được rồi, mảng sẽ ở địa chỉ 0x00A33211". Trong thời gian chạy, địa chỉ đó được dịch sang địa chỉ thực / phần cứng bởi MMU và HĐH.

Giá trị khởi tạo lưu trữ tĩnh mọi thứ là một chút khác nhau. Ví dụ:

int array[] = { 1 , 2 , 3 , 4 };

Trong ví dụ đầu tiên của chúng tôi, trình biên dịch chỉ quyết định nơi mảng sẽ được phân bổ, lưu trữ thông tin đó trong tệp thực thi.
Trong trường hợp các thứ được khởi tạo giá trị, trình biên dịch cũng đưa giá trị ban đầu của mảng vào tệp thực thi và thêm mã thông báo cho trình tải chương trình rằng sau khi cấp phát mảng khi bắt đầu chương trình, mảng phải được điền với các giá trị này.

Dưới đây là hai ví dụ về lắp ráp được tạo bởi trình biên dịch (GCC4.8.1 với mục tiêu x86):

Mã C ++:

int a[4];
int b[] = { 1 , 2 , 3 , 4 };

int main()
{}

Lắp ráp đầu ra:

a:
    .zero   16
b:
    .long   1
    .long   2
    .long   3
    .long   4
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, %eax
    popq    %rbp
    ret

Như bạn có thể thấy, các giá trị được đưa trực tiếp vào tổ hợp. Trong mảng a, trình biên dịch tạo ra khởi tạo 0 byte 16 byte, bởi vì Standard nói rằng những thứ được lưu trữ tĩnh nên được khởi tạo về 0 theo mặc định:

8.5.9 (Bộ khởi tạo) [Lưu ý]:
Mọi đối tượng có thời lượng lưu trữ tĩnh đều được khởi tạo bằng 0 khi khởi động chương trình trước khi bất kỳ khởi tạo nào khác diễn ra. Trong một số trường hợp, khởi tạo bổ sung được thực hiện sau.

Tôi luôn đề nghị mọi người tháo gỡ mã của họ để xem trình biên dịch thực sự làm gì với mã C ++. Điều này áp dụng từ các lớp lưu trữ / thời lượng (như câu hỏi này) cho đến tối ưu hóa trình biên dịch nâng cao. Bạn có thể hướng dẫn trình biên dịch của mình tạo ra lắp ráp, nhưng có những công cụ tuyệt vời để thực hiện việc này trên Internet một cách thân thiện. Yêu thích của tôi là GCC Explorer .


2
Cảm ơn. Điều này làm rõ rất nhiều. Vì vậy, trình biên dịch xuất ra một cái gì đó tương đương với "bộ nhớ dự trữ từ 0xABC đến 0xXYZ cho mảng biến [], v.v." và sau đó trình tải sử dụng điều đó để thực sự phân bổ nó ngay trước khi nó chạy chương trình?
Talha đã nói

1
@TalhaSay chính xác. Xem bản chỉnh sửa để xem ví dụ
Manu343726

2
@Secko Tôi có những thứ đơn giản hóa. Nó chỉ là một đề cập về chương trình hoạt động thông qua bộ nhớ ảo, nhưng vì câu hỏi không phải là về bộ nhớ ảo nên tôi đã không mở rộng chủ đề. Tôi chỉ chỉ ra rằng trình biên dịch có thể thực hiện các giả định về địa chỉ bộ nhớ tại thời gian biên dịch, nhờ vào bộ nhớ ảo.
Manu343726

2
@Secko vâng. mmm "tạo ra" là một thuật ngữ tốt hơn tôi nghĩ.
Manu343726

2
"Nó được phân bổ trong vùng mamory tĩnh của không gian bộ nhớ quá trình" Đọc phân bổ một số khu vực động vật có vú tĩnh trong không gian bộ nhớ quá trình của tôi.
Radiodef

27

Bộ nhớ được phân bổ tại thời gian biên dịch đơn giản có nghĩa là sẽ không có phân bổ thêm vào thời gian chạy - không có lệnh gọi đến malloc, mới hoặc các phương thức phân bổ động khác. Bạn sẽ có một lượng sử dụng bộ nhớ cố định ngay cả khi bạn không cần tất cả bộ nhớ đó mọi lúc.

Không phải là cấp phát bộ nhớ theo định nghĩa một khái niệm thời gian chạy?

Bộ nhớ không được sử dụng trước thời gian chạy, nhưng ngay lập tức trước khi thực hiện bắt đầu phân bổ của nó được xử lý bởi hệ thống.

Nếu tôi tạo biến 1KB được cấp phát tĩnh trong mã C / C ++ của mình, điều đó có làm tăng kích thước của tệp thực thi bằng cùng một lượng không?

Đơn giản chỉ cần khai báo tĩnh sẽ không tăng kích thước thực thi của bạn nhiều hơn một vài byte. Khai báo nó với giá trị ban đầu khác không (để giữ giá trị ban đầu đó). Thay vào đó, trình liên kết chỉ cần thêm số tiền 1KB này vào yêu cầu bộ nhớ mà trình tải của hệ thống tạo cho bạn ngay trước khi thực hiện.


1
nếu tôi viết static int i[4] = {2 , 3 , 5 ,5 }nó sẽ tăng theo kích thước thực thi thêm 16 byte. Bạn đã nói "Đơn giản là khai báo tĩnh sẽ không tăng kích thước của tệp thực thi của bạn nhiều hơn một vài byte. Khai báo nó với giá trị ban đầu khác không" Khai báo nó với giá trị ban đầu sẽ có ý nghĩa gì.
Suraj Jain

Thực thi của bạn có hai khu vực cho dữ liệu tĩnh - một cho thống kê chưa được khởi tạo và một cho thống kê khởi tạo. Khu vực chưa được khởi tạo thực sự chỉ là một dấu hiệu kích thước; khi chương trình của bạn được chạy, kích thước đó được sử dụng để tăng diện tích lưu trữ tĩnh nhưng bản thân chương trình không phải giữ bất cứ thứ gì nhiều hơn số lượng dữ liệu chưa được sử dụng. Đối với các thống kê được khởi tạo, chương trình của bạn không chỉ giữ kích thước của (mỗi) tĩnh mà còn cả những gì nó được khởi tạo. Vì vậy, trong ví dụ của bạn, chương trình của bạn sẽ có 2, 3, 5 và 5 trong đó.
mah

Việc triển khai được định nghĩa là nơi nó được đặt / cách phân bổ, nhưng tôi không chắc là tôi hiểu sự cần thiết phải biết.
mah

23

Bộ nhớ được phân bổ trong thời gian biên dịch có nghĩa là khi bạn tải chương trình, một phần của bộ nhớ sẽ được cấp phát ngay lập tức và kích thước và vị trí (tương đối) của phân bổ này được xác định tại thời gian biên dịch.

char a[32];
char b;
char c;

3 biến đó được "phân bổ tại thời gian biên dịch", điều đó có nghĩa là trình biên dịch tính toán kích thước của chúng (được cố định) tại thời gian biên dịch. Biến asẽ là phần bù trong bộ nhớ, giả sử, chỉ đến địa chỉ 0, bsẽ trỏ đến địa chỉ 33 và c34 (giả sử không tối ưu hóa căn chỉnh). Vì vậy, việc phân bổ 1Kb dữ liệu tĩnh sẽ không làm tăng kích thước mã của bạn , vì nó sẽ chỉ thay đổi phần bù bên trong nó. Không gian thực tế sẽ được phân bổ tại thời gian tải .

Phân bổ bộ nhớ thực luôn xảy ra trong thời gian chạy, bởi vì kernel cần theo dõi nó và cập nhật cấu trúc dữ liệu bên trong của nó (bao nhiêu bộ nhớ được phân bổ cho mỗi quá trình, trang, v.v.). Sự khác biệt là trình biên dịch đã biết kích thước của từng dữ liệu bạn sẽ sử dụng và điều này được phân bổ ngay khi chương trình của bạn được thực thi.

Cũng nhớ rằng chúng ta đang nói về địa chỉ tương đối . Địa chỉ thực nơi biến sẽ được đặt sẽ khác nhau. Khi tải, hạt nhân sẽ dự trữ một số bộ nhớ cho quá trình, giả sử tại địa chỉ xvà tất cả các địa chỉ được mã hóa cứng có trong tệp thực thi sẽ được tăng theo xbyte, do đó, biến atrong ví dụ sẽ ở địa chỉ x, b tại địa chỉ x+33và Sớm.


17

Thêm các biến trên ngăn xếp chiếm N byte không (nhất thiết) sẽ tăng kích thước của bin thêm N byte. Trên thực tế, nó sẽ thêm nhưng một vài byte hầu hết thời gian.
Hãy bắt đầu với một ví dụ về cách thêm 1000 ký tự vào mã của bạn sẽ tăng kích thước của thùng theo kiểu tuyến tính.

Nếu 1k là một chuỗi, trong số một nghìn ký tự, được khai báo như vậy

const char *c_string = "Here goes a thousand chars...999";//implicit \0 at end

và sau đó bạn đã vim your_compiled_bin, bạn thực sự có thể thấy chuỗi đó trong thùng ở đâu đó. Trong trường hợp đó, có: tệp thực thi sẽ lớn hơn 1 k, vì nó chứa chuỗi đầy đủ.
Tuy nhiên, nếu bạn phân bổ một mảng ints, chars hoặc longs trên ngăn xếp và gán nó trong một vòng lặp, một cái gì đó dọc theo các dòng này

int big_arr[1000];
for (int i=0;i<1000;++i) big_arr[i] = some_computation_func(i);

sau đó, không: nó sẽ không tăng thùng ... bởi 1000*sizeof(int)
Phân bổ vào thời gian biên dịch có nghĩa là bây giờ bạn hiểu nó nghĩa là gì (dựa trên nhận xét của bạn): thùng được biên dịch chứa thông tin mà hệ thống yêu cầu để biết bao nhiêu bộ nhớ chức năng / khối nào sẽ cần khi được thực thi, cùng với thông tin về kích thước ngăn xếp mà ứng dụng của bạn yêu cầu. Đó là những gì hệ thống sẽ phân bổ khi nó thực thi thùng của bạn và chương trình của bạn trở thành một quy trình (tốt, việc thực hiện thùng của bạn là quá trình ... tốt, bạn hiểu những gì tôi đang nói).
Tất nhiên, tôi không vẽ bức tranh đầy đủ ở đây: Thùng chứa thông tin về việc một thùng lớn sẽ thực sự cần đến như thế nào. Dựa trên thông tin này (trong số những thứ khác), hệ thống sẽ dành một phần bộ nhớ, được gọi là ngăn xếp, rằng chương trình sẽ được sắp xếp miễn phí. Bộ nhớ ngăn xếp vẫn được phân bổ bởi hệ thống, khi quá trình (kết quả của thùng của bạn được thực thi) được bắt đầu. Quá trình sau đó quản lý bộ nhớ ngăn xếp cho bạn. Khi một hàm hoặc vòng lặp (bất kỳ loại khối nào) được gọi / được thực thi, các biến cục bộ của khối đó sẽ được đẩy đến ngăn xếp và chúng bị loại bỏ (bộ nhớ ngăn xếp được "giải phóng" để nói) được sử dụng bởi người khác chức năng / khối. Vì vậy, khai báoint some_array[100]sẽ chỉ thêm một vài byte thông tin bổ sung vào thùng, thông báo cho hệ thống rằng chức năng X sẽ được yêu cầu 100*sizeof(int)+ thêm một số không gian lưu giữ sách.


Cảm ơn rất nhiều. Một câu hỏi nữa, các biến cục bộ cho các hàm cũng có được phân bổ theo cùng một cách trong thời gian biên dịch không?
Talha đã nói

@TalhaSayed: Vâng, đó là điều tôi muốn nói khi tôi nói: "thông tin hệ thống yêu cầu phải biết bao nhiêu bộ nhớ mà chức năng / khối sẽ yêu cầu." Thời điểm bạn gọi một chức năng, hệ thống sẽ phân bổ bộ nhớ cần thiết cho chức năng đó. Khoảnh khắc hàm trả về, bộ nhớ đó sẽ được giải phóng trở lại.
Elias Van Ootegem

Đối với các nhận xét trong mã C của bạn: Đó không thực sự / nhất thiết là những gì xảy ra. Chẳng hạn, chuỗi nhiều khả năng sẽ chỉ được phân bổ một lần, vào thời gian biên dịch. Do đó, nó không bao giờ được "giải phóng" (tôi cũng nghĩ rằng thuật ngữ này thường chỉ được sử dụng khi bạn phân bổ một cái gì đó một cách linh hoạt), ikhông phải là "giải phóng" hoặc là một trong hai. Nếu iđược lưu lại trên bộ nhớ, nó sẽ bị đẩy vào ngăn xếp, thứ gì đó không được giải phóng theo nghĩa của từ đó, bất chấp điều đó ihoặc csẽ được giữ trong sổ đăng ký toàn bộ thời gian. Tất nhiên, tất cả phụ thuộc vào trình biên dịch, có nghĩa là nó không phải là màu đen và trắng.
phant0m

@ phant0m: Tôi chưa bao giờ nói chuỗi được phân bổ trên ngăn xếp, chỉ có con trỏ cũng vậy, chính chuỗi sẽ nằm trong bộ nhớ chỉ đọc. Tôi biết bộ nhớ được liên kết với các biến cục bộ không được giải phóng theo nghĩa của các free()cuộc gọi, nhưng bộ nhớ ngăn xếp mà chúng sử dụng là miễn phí để sử dụng bởi các chức năng khác một khi chức năng tôi liệt kê trả về. Tôi đã xóa mã, vì nó có thể gây nhầm lẫn với một số người
Elias Van Ootegem

Ah tôi thấy. Trong trường hợp đó, hãy nhận xét của tôi có nghĩa là "Tôi đã nhầm lẫn bởi từ ngữ của bạn."
phant0m

16

Trên nhiều nền tảng, tất cả các phân bổ toàn cầu hoặc tĩnh trong mỗi mô-đun sẽ được trình biên dịch hợp nhất thành ba hoặc ít hơn các phân bổ hợp nhất (một cho dữ liệu chưa được khởi tạo (thường được gọi là "bss"), một cho dữ liệu có thể ghi được khởi tạo (thường được gọi là "dữ liệu" ) và một cho dữ liệu không đổi ("const")) và tất cả các phân bổ toàn cầu hoặc tĩnh của từng loại trong một chương trình sẽ được liên kết hợp nhất thành một toàn cầu cho mỗi loại. Ví dụ, giả sử intlà bốn byte, một mô-đun có các phân bổ tĩnh duy nhất sau đây:

int a;
const int b[6] = {1,2,3,4,5,6};
char c[200];
const int d = 23;
int e[4] = {1,2,3,4};
int f;

nó sẽ nói với trình liên kết rằng nó cần 208 byte cho bss, 16 byte cho "data" và 28 byte cho "const". Hơn nữa, mọi tham chiếu đến một biến sẽ được thay thế bằng bộ chọn vùng và phần bù, do đó, a, b, c, d và e, sẽ được thay thế bằng bss + 0, const + 0, bss + 4, const + 24, dữ liệu +0, hoặc bss + 204, tương ứng.

Khi một chương trình được liên kết, tất cả các khu vực bss từ tất cả các mô-đun được nối với nhau; tương tự như vậy các dữ liệu và các khu vực const. Đối với mỗi mô-đun, địa chỉ của bất kỳ biến tương đối bss nào sẽ được tăng theo kích thước của tất cả các vùng bss của mô-đun trước đó (một lần nữa, tương tự với dữ liệu và const). Do đó, khi trình liên kết được thực hiện, bất kỳ chương trình nào cũng sẽ có một phân bổ bss, một phân bổ dữ liệu và một phân bổ const.

Khi một chương trình được tải, một trong bốn điều thường sẽ xảy ra tùy thuộc vào nền tảng:

  1. Tệp thực thi sẽ cho biết cần bao nhiêu byte cho mỗi loại dữ liệu và - cho vùng dữ liệu được khởi tạo, nơi có thể tìm thấy nội dung ban đầu. Nó cũng sẽ bao gồm một danh sách tất cả các hướng dẫn sử dụng địa chỉ tương đối bss-, data- hoặc const-. Hệ điều hành hoặc trình tải sẽ phân bổ lượng không gian thích hợp cho từng khu vực và sau đó thêm địa chỉ bắt đầu của khu vực đó vào mỗi lệnh cần.

  2. Hệ điều hành sẽ phân bổ một đoạn bộ nhớ để chứa cả ba loại dữ liệu và cung cấp cho ứng dụng một con trỏ tới đoạn bộ nhớ đó. Bất kỳ mã nào sử dụng dữ liệu tĩnh hoặc toàn cầu sẽ quy định nó liên quan đến con trỏ đó (trong nhiều trường hợp, con trỏ sẽ được lưu trữ trong một thanh ghi cho vòng đời của một ứng dụng).

  3. Hệ điều hành ban đầu sẽ không phân bổ bất kỳ bộ nhớ nào cho ứng dụng, ngoại trừ những gì chứa mã nhị phân của nó, nhưng điều đầu tiên mà ứng dụng sẽ là yêu cầu phân bổ phù hợp từ hệ điều hành, mà nó sẽ mãi mãi giữ trong một thanh ghi.

  4. Hệ điều hành ban đầu sẽ không phân bổ không gian cho ứng dụng, nhưng ứng dụng sẽ yêu cầu phân bổ phù hợp khi khởi động (như trên). Ứng dụng sẽ bao gồm một danh sách các hướng dẫn với các địa chỉ cần được cập nhật để phản ánh nơi bộ nhớ được phân bổ (như kiểu đầu tiên), nhưng thay vì ứng dụng được vá bởi trình tải OS, ứng dụng sẽ bao gồm đủ mã để tự vá .

Tất cả bốn phương pháp đều có ưu điểm và nhược điểm. Tuy nhiên, trong mọi trường hợp, trình biên dịch sẽ hợp nhất một số lượng biến tĩnh tùy ý thành một số lượng nhỏ các yêu cầu bộ nhớ cố định và trình liên kết sẽ hợp nhất tất cả các biến đó thành một số lượng nhỏ phân bổ hợp nhất. Mặc dù một ứng dụng sẽ phải nhận một đoạn bộ nhớ từ hệ điều hành hoặc trình tải, nó là trình biên dịch và trình liên kết chịu trách nhiệm phân bổ các phần riêng lẻ trong khối lớn đó cho tất cả các biến riêng lẻ cần nó.


13

Cốt lõi của câu hỏi của bạn là: "Bộ nhớ" được phân bổ "như thế nào trong một tệp được biên dịch? Không phải bộ nhớ luôn được phân bổ trong RAM với tất cả các công cụ quản lý bộ nhớ ảo? Không phải là cấp phát bộ nhớ theo định nghĩa khái niệm thời gian chạy sao?"

Tôi nghĩ vấn đề là có hai khái niệm khác nhau liên quan đến việc cấp phát bộ nhớ. Về cơ bản, phân bổ bộ nhớ là quá trình chúng ta nói "mục dữ liệu này được lưu trữ trong khối bộ nhớ cụ thể này". Trong một hệ thống máy tính hiện đại, điều này bao gồm một quá trình gồm hai bước:

  • Một số hệ thống được sử dụng để quyết định địa chỉ ảo mà tại đó mục sẽ được lưu trữ
  • Địa chỉ ảo được ánh xạ tới một địa chỉ vật lý

Quá trình thứ hai hoàn toàn là thời gian chạy, nhưng quá trình trước có thể được thực hiện tại thời gian biên dịch, nếu dữ liệu có kích thước đã biết và một số lượng cố định là bắt buộc. Về cơ bản đây là cách nó hoạt động:

  • Trình biên dịch thấy một tệp nguồn chứa một dòng trông giống như thế này:

    int c;
  • Nó tạo đầu ra cho trình biên dịch lệnh hướng dẫn nó dự trữ bộ nhớ cho biến 'c'. Điều này có thể trông như thế này:

    global _c
    section .bss
    _c: resb 4
  • Khi trình biên dịch chạy, nó sẽ giữ một bộ đếm theo dõi độ lệch của từng mục từ khi bắt đầu bộ nhớ 'phân đoạn' (hoặc 'phần'). Điều này giống như các phần của một "cấu trúc" rất lớn chứa mọi thứ trong toàn bộ tệp mà nó không có bất kỳ bộ nhớ thực tế nào được phân bổ cho nó tại thời điểm này và có thể ở bất cứ đâu. Nó ghi chú trong một bảng _ccó độ lệch cụ thể (giả sử 510 byte từ đầu đoạn) và sau đó tăng bộ đếm của nó lên 4, vì vậy biến tiếp theo sẽ ở mức (ví dụ) 514 byte. Đối với bất kỳ mã nào cần địa chỉ của _cnó, nó chỉ đặt 510 trong tệp đầu ra và thêm một lưu ý rằng đầu ra cần địa chỉ của phân đoạn chứa _cthêm vào sau đó.

  • Trình liên kết lấy tất cả các tệp đầu ra của trình biên dịch và kiểm tra chúng. Nó xác định một địa chỉ cho từng phân đoạn để chúng không trùng nhau và thêm các độ lệch cần thiết để các hướng dẫn vẫn tham chiếu đến các mục dữ liệu chính xác. Trong trường hợp bộ nhớ chưa được khởi tạo như thế bị chiếm bởic. bao nhiêu cần phải được bảo lưu. Nó có thể được định vị lại (và thường là) nhưng thường được thiết kế để được tải hiệu quả hơn tại một địa chỉ bộ nhớ cụ thể và HĐH sẽ cố gắng tải nó tại địa chỉ này. Tại thời điểm này, chúng tôi có một ý tưởng khá hay về địa chỉ ảo sẽ được sử dụng bởi cái gì c.

  • Địa chỉ vật lý sẽ không thực sự được xác định cho đến khi chương trình đang chạy. Tuy nhiên, từ quan điểm của lập trình viên, địa chỉ vật lý thực sự không liên quan, chúng tôi thậm chí sẽ không bao giờ tìm hiểu nó là gì, bởi vì HĐH thường không bận tâm đến việc nói với bất kỳ ai, nó có thể thay đổi thường xuyên (ngay cả khi chương trình đang chạy) và Mục đích chính của HĐH là dù sao đi nữa.


9

Một thực thi mô tả không gian để phân bổ cho các biến tĩnh. Việc phân bổ này được thực hiện bởi hệ thống, khi bạn chạy tệp thực thi. Vì vậy, biến tĩnh 1kB của bạn sẽ không tăng kích thước của tệp thực thi với 1kB:

static char[1024];

Tất nhiên trừ khi bạn chỉ định một trình khởi tạo:

static char[1024] = { 1, 2, 3, 4, ... };

Vì vậy, ngoài 'ngôn ngữ máy' (tức là hướng dẫn CPU), một tệp thực thi chứa mô tả về bố cục bộ nhớ được yêu cầu.


5

Bộ nhớ có thể được phân bổ theo nhiều cách:

  • trong heap ứng dụng (toàn bộ heap được phân bổ cho ứng dụng của bạn bởi OS khi chương trình bắt đầu)
  • trong heap hệ điều hành (vì vậy bạn có thể lấy nhiều hơn và nhiều hơn nữa)
  • trong đống rác được kiểm soát (giống như cả hai ở trên)
  • trên ngăn xếp (để bạn có thể nhận được một ngăn xếp tràn)
  • dành riêng trong đoạn mã / dữ liệu của nhị phân của bạn (thực thi)
  • ở nơi xa (tệp, mạng - và bạn nhận được một tay cầm không phải là con trỏ tới bộ nhớ đó)

Bây giờ câu hỏi của bạn là "bộ nhớ được phân bổ tại thời gian biên dịch" là gì. Chắc chắn đó chỉ là một câu nói không chính xác, được cho là đề cập đến phân bổ nhị phân hoặc phân bổ ngăn xếp, hoặc trong một số trường hợp thậm chí là phân bổ heap, nhưng trong trường hợp đó, phân bổ bị ẩn khỏi mắt lập trình viên bởi lệnh gọi của nhà xây dựng vô hình. Hoặc có lẽ người nói rằng chỉ muốn nói rằng bộ nhớ không được phân bổ theo heap, nhưng không biết về phân bổ ngăn xếp hoặc phân khúc. (Hoặc không muốn đi vào loại chi tiết đó).

Nhưng trong hầu hết các trường hợp, mọi người chỉ muốn nói rằng số lượng bộ nhớ được phân bổ được biết đến vào thời gian biên dịch .

Kích thước nhị phân sẽ chỉ thay đổi khi bộ nhớ được dành riêng trong mã hoặc phân đoạn dữ liệu của ứng dụng của bạn.


1
Câu trả lời này gây nhầm lẫn (hoặc nhầm lẫn) ở chỗ nó nói về "heap ứng dụng", "heap OS" và "heap GC" như thể đây là tất cả các khái niệm có ý nghĩa. Tôi suy luận rằng bởi # 1 bạn đã cố gắng nói rằng một số ngôn ngữ lập trình có thể (giả sử) sử dụng sơ đồ "phân bổ heap" phân bổ bộ nhớ ra khỏi bộ đệm có kích thước cố định trong phần .data, nhưng điều đó dường như không thực tế để gây hại theo sự hiểu biết của OP. Re # 2 và # 3, sự hiện diện của một GC không thực sự thay đổi bất cứ điều gì. Và lại # 5, bạn đã bỏ qua sự khác biệt quan trọng hơn NHIỀU giữa .data.bss.
Quuxplusone

4

Bạn đúng rồi. Bộ nhớ thực sự được phân bổ (phân trang) tại thời điểm tải, tức là khi tệp thực thi được đưa vào bộ nhớ (ảo). Bộ nhớ cũng có thể được khởi tạo vào thời điểm đó. Trình biên dịch chỉ tạo ra một bản đồ bộ nhớ. [Nhân tiện, không gian ngăn xếp và đống cũng được phân bổ tại thời gian tải!]


2

Tôi nghĩ bạn cần lùi lại một chút. Bộ nhớ được phân bổ tại thời gian biên dịch .... Điều đó có nghĩa là gì? Điều đó có nghĩa là bộ nhớ trên các chip chưa được sản xuất, đối với các máy tính chưa được thiết kế, bằng cách nào đó đã được bảo lưu? Không. Du hành thời gian, không có trình biên dịch nào có thể thao túng vũ trụ.

Vì vậy, nó phải có nghĩa là trình biên dịch tạo ra các hướng dẫn để phân bổ bộ nhớ đó bằng cách nào đó trong thời gian chạy. Nhưng nếu bạn nhìn nó từ góc bên phải, trình biên dịch sẽ tạo ra tất cả các hướng dẫn, vì vậy điều gì có thể là sự khác biệt. Sự khác biệt là trình biên dịch quyết định và trong thời gian chạy, mã của bạn không thể thay đổi hoặc sửa đổi các quyết định của nó. Nếu nó quyết định cần 50 byte khi biên dịch, tại thời gian chạy, bạn không thể khiến nó quyết định phân bổ 60 - quyết định đó đã được đưa ra.


Tôi thích các câu trả lời sử dụng phương pháp Socratic, nhưng tôi vẫn đánh giá thấp bạn về kết luận sai lầm rằng "trình biên dịch tạo ra các hướng dẫn để phân bổ bộ nhớ đó bằng cách nào đó trong thời gian chạy". Kiểm tra câu trả lời được bình chọn hàng đầu để xem cách trình biên dịch có thể "cấp phát bộ nhớ" mà không tạo ra bất kỳ "hướng dẫn" thời gian chạy nào. (Lưu ý rằng "hướng dẫn" trong ngữ cảnh ngôn ngữ lắp ráp có ý nghĩa cụ thể, nghĩa là các mã thực thi. Bạn có thể đã sử dụng từ thông tục để có nghĩa là "công thức", nhưng trong ngữ cảnh này sẽ chỉ gây nhầm lẫn cho OP. )
Quuxplusone

1
@Quuxplusone: Tôi đọc (và nâng cấp) câu trả lời đó. Và không, câu trả lời của tôi không đề cập cụ thể đến vấn đề của các biến khởi tạo. Nó cũng không giải quyết mã tự sửa đổi. Mặc dù câu trả lời đó là tuyệt vời, nhưng nó không giải quyết được vấn đề tôi coi là vấn đề quan trọng - đặt mọi thứ vào bối cảnh. Do đó, câu trả lời của tôi, mà tôi hy vọng sẽ giúp OP (và những người khác) dừng lại và suy nghĩ về những gì đang hoặc có thể xảy ra, khi họ có vấn đề mà họ không hiểu.
jmoreno

@Quuxplusone: Xin lỗi nếu tôi đưa ra những cáo buộc sai ở đây, nhưng tôi cho rằng bạn cũng là một trong những người đã trả lời câu hỏi của tôi. Nếu vậy, bạn có phiền khi chỉ ra phần nào trong câu trả lời của tôi là lý do chính để làm như vậy không, và bạn cũng quan tâm kiểm tra chỉnh sửa của tôi chứ? Tôi biết rằng tôi đã bỏ qua một vài thông tin về nội bộ thực sự về cách quản lý bộ nhớ ngăn xếp, vì vậy bây giờ tôi đã thêm một chút về việc tôi không chính xác 100% cho câu trả lời của mình dù sao đi nữa :)
Elias Van Ootegem

@jmoreno Điểm mà bạn đưa ra về "Có thể có nghĩa là bộ nhớ trên các chip chưa được sản xuất, đối với các máy tính chưa được thiết kế, bằng cách nào đó đã được bảo lưu?" chính xác là nghĩa sai mà từ "phân bổ" ngụ ý khiến tôi bối rối ngay từ đầu. Tôi thích câu trả lời này vì nó đề cập chính xác vấn đề mà tôi đang cố gắng chỉ ra. Không có câu trả lời nào ở đây thực sự chạm vào điểm đặc biệt đó. Cảm ơn.
Talha đã nói

2

Nếu bạn học lập trình lắp ráp, bạn sẽ thấy rằng bạn phải khắc các phân đoạn cho dữ liệu, ngăn xếp và mã, v.v ... Phân đoạn dữ liệu là nơi các chuỗi và số của bạn sống. Đoạn mã là nơi mã của bạn sống. Các phân đoạn này được xây dựng vào chương trình thực thi. Tất nhiên kích thước ngăn xếp cũng quan trọng ... bạn sẽ không muốn tràn ngăn xếp !

Vì vậy, nếu phân đoạn dữ liệu của bạn là 500 byte, chương trình của bạn có vùng 500 byte. Nếu bạn thay đổi phân đoạn dữ liệu thành 1500 byte, kích thước của chương trình sẽ lớn hơn 1000 byte. Dữ liệu được lắp ráp vào chương trình thực tế.

Đây là những gì đang xảy ra khi bạn biên dịch các ngôn ngữ cấp cao hơn. Vùng dữ liệu thực tế được phân bổ khi nó được biên dịch thành một chương trình thực thi, làm tăng kích thước của chương trình. Chương trình cũng có thể yêu cầu bộ nhớ khi đang bay và đây là bộ nhớ động. Bạn có thể yêu cầu bộ nhớ từ RAM và CPU sẽ cung cấp cho bạn để sử dụng, bạn có thể loại bỏ nó và trình thu gom rác của bạn sẽ giải phóng nó trở lại CPU. Nó thậm chí có thể được trao đổi vào một đĩa cứng, nếu cần thiết, bởi một trình quản lý bộ nhớ tốt. Những tính năng này là những gì ngôn ngữ cấp cao cung cấp cho bạn.


2

Tôi muốn giải thích các khái niệm này với sự giúp đỡ của một vài sơ đồ.

Điều này đúng là bộ nhớ không thể được phân bổ tại thời gian biên dịch, chắc chắn. Nhưng, sau đó những gì xảy ra trong thực tế tại thời gian biên dịch.

Đây là lời giải thích. Nói, ví dụ một chương trình có bốn biến x, y, z và k. Bây giờ, tại thời điểm biên dịch, nó chỉ đơn giản là tạo một bản đồ bộ nhớ, trong đó vị trí của các biến này đối với nhau được xác định. Sơ đồ này sẽ minh họa nó tốt hơn.

Bây giờ hãy tưởng tượng, không có chương trình nào đang chạy trong bộ nhớ. Điều này tôi hiển thị bởi một hình chữ nhật trống lớn.

cánh đồng trống

Tiếp theo, phiên bản đầu tiên của chương trình này được thực thi. Bạn có thể hình dung nó như sau. Đây là thời gian mà bộ nhớ thực sự được phân bổ.

sơ thẩm

Khi phiên bản thứ hai của chương trình này đang chạy, bộ nhớ sẽ trông như sau.

ví dụ thứ hai

Và thứ ba ..

ví dụ thứ ba

Vv và Vv.

Tôi hy vọng trực quan này giải thích khái niệm này tốt.


2
Nếu những sơ đồ đó cho thấy sự khác biệt giữa bộ nhớ tĩnh và bộ nhớ động, chúng sẽ hữu ích hơn IMHO.
Bartek Banachewicz

Điều này đã được tôi cố tình tránh để giữ cho mọi thứ đơn giản. Trọng tâm của tôi là để giải thích rõ ràng về quỹ này mà không có nhiều lộn xộn kỹ thuật. Theo như điều này có nghĩa là biến tĩnh .. Điểm này đã được thiết lập tốt bởi các câu trả lời trước. Vì vậy, tôi đã bỏ qua điều này.
dùng3258051

1
Ơ, khái niệm này không đặc biệt phức tạp, vì vậy tôi không hiểu tại sao làm cho nó đơn giản hơn nó cần, nhưng vì nó chỉ có ý nghĩa như một câu trả lời miễn phí, ok.
Bartek Banachewicz

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.