Tại sao phân đoạn .bss là bắt buộc?


120

Những gì tôi biết là các biến toàn cục và tĩnh được lưu trữ trong .dataphân đoạn và dữ liệu chưa được khởi tạo nằm trong .bssphân đoạn. Điều tôi không hiểu là tại sao chúng tôi có phân đoạn dành riêng cho các biến chưa được khởi tạo? Nếu một biến chưa được khởi tạo có giá trị được chỉ định tại thời điểm chạy, liệu biến đó có tồn tại chỉ trong .bssphân đoạn không?

Trong chương trình sau đây, alà trong .dataphân đoạn, và blà trong .bssphân đoạn; đúng không? Vui lòng sửa cho tôi nếu sự hiểu biết của tôi là sai.

#include <stdio.h>
#include <stdlib.h>

int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9};
int b[20]; /* Uninitialized, so in the .bss and will not occupy space for 20 * sizeof (int) */

int main ()
{
   ;
}  

Ngoài ra, hãy xem xét chương trình sau,

#include <stdio.h>
#include <stdlib.h>
int var[10];  /* Uninitialized so in .bss */
int main ()
{
   var[0] = 20  /* **Initialized, where this 'var' will be ?** */
}

3
Bạn có thể đọc BSS là Tiết kiệm không gian tốt hơn .
smwikipedia

Câu trả lời:


89

Lý do là để giảm dung lượng chương trình. Hãy tưởng tượng rằng chương trình C của bạn chạy trên một hệ thống nhúng, nơi mã và tất cả các hằng số được lưu trong ROM thực (bộ nhớ flash). Trong các hệ thống như vậy, một "bản sao xuống" ban đầu phải được thực thi để đặt tất cả các đối tượng thời lượng lưu trữ tĩnh, trước khi hàm main () được gọi. Nó thường sẽ giống như sau:

for(i=0; i<all_explicitly_initialized_objects; i++)
{
  .data[i] = init_value[i];
}

memset(.bss, 
       0, 
       all_implicitly_initialized_objects);

Nơi .data và .bss được lưu trong RAM, nhưng init_value được lưu trong ROM. Nếu nó là một phân đoạn, thì ROM phải được lấp đầy bằng rất nhiều số 0, làm tăng kích thước ROM lên đáng kể.

Các tệp thực thi dựa trên RAM hoạt động tương tự, mặc dù tất nhiên chúng không có ROM thực sự.

Ngoài ra, memset có thể là một trình hợp dịch nội tuyến rất hiệu quả, có nghĩa là bản sao chép khởi động có thể được thực thi nhanh hơn.


7
Để làm rõ: sự khác biệt duy nhất giữa .data và .bss là khi khởi động, "copy-down" có thể được chạy tuần tự, do đó nhanh hơn. Nếu nó không được chia thành hai đoạn thì quá trình khởi tạo sẽ phải bỏ qua các điểm RAM thuộc về các biến chưa được khởi tạo, do đó lãng phí thời gian.
CL22

80

Các .bssbộ phận là một tối ưu hóa. Toàn bộ .bssphân đoạn được mô tả bằng một số duy nhất, có thể là 4 byte hoặc 8 byte, cung cấp kích thước của nó trong quá trình chạy, trong khi .dataphần này lớn bằng tổng kích thước của các biến được khởi tạo. Do đó, .bsslàm cho các tệp thực thi nhỏ hơn và tải nhanh hơn. Nếu không, các biến có thể nằm trong .dataphân đoạn với sự khởi tạo rõ ràng thành số 0; chương trình sẽ khó nhận ra sự khác biệt. (Về chi tiết, địa chỉ của các đối tượng trong .bsscó thể sẽ khác với địa chỉ nếu nó nằm trong .dataphân khúc.)

Trong chương trình đầu tiên, asẽ nằm trong .dataphân đoạn và bsẽ nằm trong .bssphân đoạn của tệp thực thi. Sau khi chương trình được tải, sự khác biệt trở nên không quan trọng. Tại thời gian chạy, bchiếm 20 * sizeof(int)byte.

Trong chương trình thứ hai, varkhông gian được cấp phát và việc gán trong main()sửa đổi không gian đó. Điều đó xảy ra là không gian cho varđược mô tả trong .bssphân đoạn chứ không phải .dataphân đoạn, nhưng điều đó không ảnh hưởng đến cách chương trình hoạt động khi chạy.


16
Ví dụ: hãy xem xét có nhiều bộ đệm chưa khởi tạo có độ dài 4096 byte. Bạn có muốn tất cả các bộ đệm 4k đó đóng góp vào kích thước của hệ nhị phân không? Đó sẽ là rất nhiều không gian lãng phí.
Jeff Mercado

1
@jonathen killer: Tại sao toàn bộ phân đoạn bss được mô tả bằng một số duy nhất ??
Suraj Jain

@JonathanLeffler Ý tôi là tất cả biến tĩnh khởi tạo bằng 0 đều được chuyển trong bss. Vì vậy, không nên giá trị của nó chỉ là 0? Và cũng tại sao chúng không được cung cấp không gian trên phần .data, làm thế nào để làm như vậy làm cho nó chậm?
Suraj Jain

2
@SurajJain: số được lưu trữ là số byte được điền bằng số không. Trừ khi không có biến nào chưa được khởi tạo như vậy, độ dài của phần bss sẽ không bằng 0, mặc dù tất cả các byte I của phần bss sẽ bằng 0 khi chương trình được tải.
Jonathan Leffler

1
Phần .bss trong tệp thực thi chỉ đơn giản là một số. Phần .bss trong hình ảnh tiến trình trong bộ nhớ thường là bộ nhớ liền kề với phần .data và thường phần .data thời gian chạy được kết hợp với .bss; không có sự khác biệt được thực hiện trong bộ nhớ thời gian chạy. Đôi khi, bạn có thể tìm thấy nơi bss bắt đầu ( edata). Về mặt thực tế, .bss không tồn tại trong bộ nhớ sau khi hình ảnh quá trình hoàn thành; dữ liệu zeroed là một phần đơn giản của phần .data. Nhưng các chi tiết khác nhau tùy thuộc vào o / s, v.v.
Jonathan Leffler

15

Từ hợp ngữ từng bước: Lập trình với Linux của Jeff Duntemann, liên quan đến phần .data :

Phần .data chứa các định nghĩa dữ liệu của các mục dữ liệu được khởi tạo. Dữ liệu khởi tạo là dữ liệu có giá trị trước khi chương trình bắt đầu chạy. Các giá trị này là một phần của tệp thực thi. Chúng được tải vào bộ nhớ khi tệp thực thi được tải vào bộ nhớ để thực thi.

Điều quan trọng cần nhớ về phần .data là bạn xác định càng nhiều mục dữ liệu được khởi tạo, tệp thực thi sẽ càng lớn và thời gian tải tệp từ đĩa vào bộ nhớ khi bạn chạy càng lâu.

và phần .bss :

Không phải tất cả các mục dữ liệu cần phải có giá trị trước khi chương trình bắt đầu chạy. Ví dụ: khi bạn đang đọc dữ liệu từ tệp đĩa, bạn cần phải có một nơi để dữ liệu đi vào sau khi nó vào từ đĩa. Bộ đệm dữ liệu như vậy được xác định trong phần .bss của chương trình của bạn. Bạn dành một số byte cho bộ đệm và đặt tên cho bộ đệm, nhưng bạn không nói giá trị nào sẽ có trong bộ đệm.

Có một sự khác biệt quan trọng giữa các mục dữ liệu được xác định trong phần .data và các mục dữ liệu được xác định trong phần .bss: các mục dữ liệu trong phần .data thêm vào kích thước tệp thực thi của bạn. Các mục dữ liệu trong phần .bss thì không. Một bộ đệm chiếm 16.000 byte (hoặc hơn, đôi khi nhiều hơn) có thể được định nghĩa bằng .bss và hầu như không thêm gì (khoảng 50 byte cho mô tả) vào kích thước tệp thực thi.


9

Trước hết, những biến đó trong ví dụ của bạn không phải là chưa được khởi tạo; C chỉ định rằng các biến tĩnh không được khởi tạo khác sẽ được khởi tạo bằng 0.

Vì vậy, lý do cho .bss là có các tệp thực thi nhỏ hơn, tiết kiệm không gian và cho phép tải chương trình nhanh hơn, vì trình tải chỉ có thể cấp phát một loạt các số 0 thay vì phải sao chép dữ liệu từ đĩa.

Khi chạy chương trình, trình nạp chương trình sẽ tải .data và .bss vào bộ nhớ. Việc ghi vào các đối tượng nằm trong .data hoặc .bss do đó chỉ đi vào bộ nhớ, chúng không được chuyển sang tệp nhị phân trên đĩa bất kỳ lúc nào.


5

Các hệ thống V ABI 4.1 (1997) (AKA ELF đặc điểm kỹ thuật) cũng chứa câu trả lời:

.bssPhần này chứa dữ liệu chưa được khởi tạo đóng góp vào hình ảnh bộ nhớ của chương trình. Theo định nghĩa, hệ thống khởi tạo dữ liệu bằng các số không khi chương trình bắt đầu chạy. Phần không chiếm dung lượng tệp, như được chỉ ra bởi loại phần SHT_NOBITS,.

nói rằng tên phần .bssđược dành riêng và có các hiệu ứng đặc biệt, đặc biệt là nó không chiếm không gian tệp , do đó lợi thế hơn .data.

Nhược điểm của tất nhiên là tất cả các byte phải được đặt thành 0khi hệ điều hành đưa chúng vào bộ nhớ, điều này hạn chế hơn, nhưng là trường hợp sử dụng phổ biến và hoạt động tốt đối với các biến chưa được khởi tạo.

Các SHT_NOBITStài liệu loại phần lặp đi lặp lại lời khẳng định rằng:

sh_sizeThành viên này cung cấp kích thước của phần tính bằng byte. Trừ khi là loại phần SHT_NOBITS, phần đó sẽ chiếm sh_size byte trong tệp. Một phần của loại SHT_NOBITScó thể có kích thước khác 0, nhưng nó không chiếm không gian trong tệp.

Tiêu chuẩn C không nói gì về các phần, nhưng chúng ta có thể dễ dàng xác minh nơi biến được lưu trữ trong Linux với objdumpreadelf, và kết luận rằng các hình cầu chưa khởi tạo trên thực tế được lưu trữ trong .bss. Hãy xem ví dụ câu trả lời này: Điều gì xảy ra với một biến được khai báo, chưa được khởi tạo trong C?


3

Bài báo wikipedia .bss cung cấp một giải thích lịch sử tốt đẹp, cho rằng thuật ngữ này có từ giữa những năm 1950 (yippee my birthday ;-).

Ngày trước, mọi bit đều quý giá, vì vậy bất kỳ phương pháp nào để báo hiệu không gian trống dự trữ đều hữu ích. Đây ( .bss ) là một trong những đã bị mắc kẹt.

Các phần .data dành cho không gian không trống, thay vào đó nó sẽ có các giá trị được xác định (của bạn) được nhập vào đó.

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.