2009-03-23 15 views
37

Sắp xếp ngăn xếp là gì? Tại sao nó được sử dụng? Nó có thể được kiểm soát bởi các thiết lập trình biên dịch không?"Sắp xếp ngăn xếp" là gì?

Chi tiết của câu hỏi này được lấy từ một vấn đề phải đối mặt khi cố gắng sử dụng thư viện ffmpeg với msvc, tuy nhiên điều tôi thực sự quan tâm là giải thích về "sắp xếp ngăn xếp" là gì.

Các chi tiết:

  • Khi runnig msvc tôi chương trình liên kết với avcodec tôi nhận được lỗi sau tuân thủ: "Trình biên dịch không sắp xếp stack biến libavcodec đã được miscompiled", tiếp theo là một vụ tai nạn trong avcodec.dll.
  • avcodec.dll không được biên soạn với msvc, vì vậy tôi không thể thấy những gì đang diễn ra bên trong.
  • Khi chạy ffmpeg.exe và sử dụng cùng một avcodec.dll mọi thứ hoạt động tốt.
  • ffmpeg.exe không được biên soạn với MSVC, nó đã tuân thủ gcc/mingw (giống như avcodec.dll)

Cảm ơn,

Dan

+1

Vì những người khác đã giải thích sắp xếp ngăn xếp là gì và tại sao nó được sử dụng, tôi chỉ muốn thêm hai xu của tôi về _ "Nó có thể được kiểm soát bởi các thiết lập trình biên dịch không?" _. Xem [câu hỏi này] (http://stackoverflow.com/questions/5496045/why-is-my-stack-pointer-only-incrementing-in-multiples-of-16?lq=1) – andreee

Trả lời

91

Alignment của các biến trong bộ nhớ (một Truyện ngắn).

Trong các máy tính trước đây có dữ liệu 8 bit. Điều này có nghĩa, mỗi chu kỳ xung nhịp 8 bit thông tin có thể được xử lý. Vậy thì tốt thôi.

Sau đó, đến máy tính 16 bit. Do khả năng tương thích xuống và các vấn đề khác, byte 8 bit được giữ lại và từ 16 bit được giới thiệu. Mỗi từ là 2 byte. Và mỗi chu kỳ đồng hồ 16 bit thông tin có thể được xử lý. Nhưng điều này đặt ra một vấn đề nhỏ.

Hãy nhìn vào bản đồ bộ nhớ:

+----+ 
|0000| 
|0001| 
+----+ 
|0002| 
|0003| 
+----+ 
|0004| 
|0005| 
+----+ 
| .. | 

Tại mỗi địa chỉ có một byte có thể được truy cập riêng rẽ. Nhưng các từ chỉ có thể được tìm nạp tại các địa chỉ đồng đều. Vì vậy, nếu chúng ta đọc một từ tại 0000, chúng ta đọc các byte tại 0000 và 0001. Nhưng nếu chúng ta muốn đọc từ tại vị trí 0001, chúng ta cần hai truy cập đọc. Đầu tiên 0000,0001 và sau đó 0002,0003 và chúng tôi chỉ giữ 0001.0002.

Tất nhiên việc này mất thêm thời gian và điều đó không được đánh giá cao. Vì vậy, đó là lý do tại sao họ phát minh ra sự liên kết. Vì vậy, chúng tôi lưu trữ các biến từ ở các ranh giới từ và các biến byte tại các ranh giới byte.

Ví dụ, nếu chúng ta có một cấu trúc với một lĩnh vực byte (B) và một lĩnh vực từ (W) (và một trình biên dịch rất ngây thơ), chúng tôi nhận được như sau:

+----+ 
|0000| B 
|0001| W 
+----+ 
|0002| W 
|0003| 
+----+ 

Mà không phải là niềm vui . Nhưng khi sử dụng căn chỉnh từ chúng tôi tìm thấy:

+----+ 
|0000| B 
|0001| - 
+----+ 
|0002| W 
|0003| W 
+----+ 

Bộ nhớ ở đây được hy sinh cho tốc độ truy cập.

Bạn có thể tưởng tượng rằng khi sử dụng từ kép (4 byte) hoặc từ bốn (8 byte), điều này thậm chí còn quan trọng hơn. Đó là lý do tại sao với hầu hết các trình biên dịch hiện đại, bạn có thể chọn liên kết bạn đang sử dụng trong khi biên dịch chương trình.

+3

Mô tả tuyệt vời về sắp xếp ngăn xếp ! –

+0

Tôi đang cố gắng học lắp ráp, và tôi đã đấu tranh với sự liên kết hiểu biết. Điều này hoàn toàn trả lời câu hỏi của tôi! – joek1975

+0

Luôn sẵn lòng giúp đỡ ai đó :-). –

11

IIRC, sắp xếp ngăn xếp là khi các biến được đặt trên ngăn xếp "căn chỉnh" với một số lượng byte cụ thể. Vì vậy, nếu bạn đang sử dụng sắp xếp chồng 16 bit, mỗi biến trên ngăn xếp sẽ bắt đầu từ một byte là bội số của 2 byte từ con trỏ ngăn xếp hiện tại trong một hàm.

Điều này có nghĩa là nếu bạn sử dụng biến số < 2 byte, chẳng hạn như char (1 byte), sẽ có 8 bit không sử dụng "đệm" giữa biến và biến tiếp theo. Điều này cho phép tối ưu hóa nhất định với các giả định dựa trên các vị trí biến.

Khi gọi các hàm, một phương thức chuyển đối số cho hàm tiếp theo là đặt chúng trên ngăn xếp (trái với việc đặt chúng trực tiếp vào thanh ghi). Có hay không căn chỉnh đang được sử dụng ở đây là quan trọng, như chức năng gọi điện thoại đặt các biến trên ngăn xếp, được đọc bởi chức năng gọi bằng cách sử dụng bù đắp. Nếu hàm gọi điện sắp xếp các biến, và hàm được gọi mong đợi chúng không được căn chỉnh, thì hàm được gọi sẽ không thể tìm thấy chúng.

Dường như mã được biên dịch msvc không đồng ý về căn chỉnh biến. Thử biên dịch với tất cả các tối ưu hóa bị tắt.

+1

sizeof (char) luôn luôn là 1 byte, mà luôn luôn là ít nhất 8 bit ... không phải byte. Alignment phụ thuộc vào nền tảng trình biên dịch, và (x86, anyway) nói chung là 4byte cho kiến ​​trúc 32bit, 8byte cho các vòm 64bit. – snemarch

+1

Cảm ơn, là một brainfart thực sự trên kích thước của một byte: P. Tôi đã chọn 16 byte như một ví dụ tùy ý, nhưng sử dụng ví dụ nhỏ hơn làm cho nó rõ ràng hơn nhiều. –

10

Một số kiến ​​trúc CPU yêu cầu sự liên kết cụ thể của các kiểu dữ liệu khác nhau và sẽ ném ngoại lệ nếu bạn không tôn trọng quy tắc này. Trong chế độ tiêu chuẩn, x86 không yêu cầu điều này đối với các kiểu dữ liệu cơ bản, nhưng có thể bị xử phạt (xem www.agner.org để biết các mẹo tối ưu hóa mức thấp).

Tuy nhiên, bộ hướng dẫn âm thanh/video SSE (thường được sử dụng cho hiệu suất cao) có yêu cầu căn chỉnh chặt chẽ và sẽ ném ngoại lệ nếu bạn cố gắng sử dụng nó trên dữ liệu chưa được ký (trừ khi bạn sử dụng phiên bản không được ký hiệu chậm hơn nhiều).

vấn đề của bạn là lẽ rằng một trình biên dịch hy vọng gọi để giữ chồng thẳng hàng, trong khi người kia hy vọng callee để sắp xếp các ngăn xếp khi cần thiết.

EDIT: về lý do ngoại lệ xảy ra, một thường trình trong DLL có thể muốn sử dụng lệnh SSE trên một số dữ liệu ngăn tạm thời và không thành công.

2

Theo như tôi biết, trình biên dịch thường không căn chỉnh các biến nằm trên ngăn xếp. Thư viện có thể tùy thuộc vào một số tùy chọn trình biên dịch không được hỗ trợ trên trình biên dịch của bạn. Sửa lỗi thông thường là khai báo các biến cần được căn chỉnh là tĩnh, nhưng nếu bạn làm điều này trong mã của người khác, bạn sẽ muốn chắc chắn rằng các biến được đề cập được khởi tạo sau này trong hàm thay vì trong khai báo.

// Some compilers won't align this as it's on the stack... 
int __declspec(align(32)) needsToBe32Aligned = 0; 
// Change to 
static int __declspec(align(32)) needsToBe32Aligned; 
needsToBe32Aligned = 0; 

Cách khác, tìm trình chuyển đổi trình biên dịch căn chỉnh các biến trên ngăn xếp. Rõ ràng cú pháp sắp xếp "__declspec" mà tôi đã sử dụng ở đây có thể không phải là trình biên dịch của bạn sử dụng.