Tôi đang chuẩn bị một số tài liệu đào tạo về C và tôi muốn các ví dụ của mình phù hợp với mô hình ngăn xếp điển hình.
Ngăn xếp C phát triển theo hướng nào trong Linux, Windows, Mac OSX (PPC và x86), Solaris và các Unix gần đây nhất?
Tôi đang chuẩn bị một số tài liệu đào tạo về C và tôi muốn các ví dụ của mình phù hợp với mô hình ngăn xếp điển hình.
Ngăn xếp C phát triển theo hướng nào trong Linux, Windows, Mac OSX (PPC và x86), Solaris và các Unix gần đây nhất?
Câu trả lời:
Sự tăng trưởng của ngăn xếp thường không phụ thuộc vào bản thân hệ điều hành mà phụ thuộc vào bộ xử lý mà nó đang chạy. Ví dụ, Solaris chạy trên x86 và SPARC. Mac OSX (như bạn đã đề cập) chạy trên PPC và x86. Linux chạy trên mọi thứ, từ Hệ thống honkin 'z lớn của tôi tại nơi làm việc cho đến một chiếc đồng hồ đeo tay nhỏ bé .
Nếu CPU cung cấp bất kỳ loại lựa chọn nào, quy ước ABI / gọi được sử dụng bởi Hệ điều hành sẽ chỉ định lựa chọn nào bạn cần thực hiện nếu bạn muốn mã của mình gọi mã của người khác.
Các bộ xử lý và hướng của chúng là:
Hiển thị tuổi của tôi trên số cuối cùng đó, 1802 là con chip được sử dụng để điều khiển các tàu con thoi sớm (tôi nghi ngờ nếu cửa đã mở, dựa trên sức mạnh xử lý mà nó có :-) và máy tính thứ hai của tôi, COMX-35 ( sau ZX80 của tôi ).
Chi tiết PDP11 được thu thập từ đây , chi tiết 8051 từ đây .
Kiến trúc SPARC sử dụng mô hình thanh ghi cửa sổ trượt. Các chi tiết có thể nhìn thấy về mặt kiến trúc cũng bao gồm một bộ đệm hình tròn gồm các cửa sổ đăng ký hợp lệ và được lưu vào bộ nhớ cache bên trong, với các bẫy khi dòng chảy quá mức / thiếu. Xem chi tiết tại đây . Như hướng dẫn sử dụng SPARCv8 giải thích , hướng dẫn LƯU và KHÔI PHỤC giống như hướng dẫn THÊM cộng với xoay cửa sổ thanh ghi. Sử dụng hằng số dương thay vì hằng số âm thông thường sẽ tạo ra một ngăn xếp tăng dần lên.
Kỹ thuật SCRT đã đề cập ở trên là một kỹ thuật khác - năm 1802 đã sử dụng một số hoặc đó là mười sáu thanh ghi 16 bit cho SCRT (kỹ thuật gọi và trả về tiêu chuẩn). Một là bộ đếm chương trình, bạn có thể sử dụng bất kỳ thanh ghi nào làm PC với SEP Rn
hướng dẫn. Một là con trỏ ngăn xếp và hai là con trỏ luôn được đặt để trỏ đến địa chỉ mã SCRT, một để gọi, một để trả về. Không có sổ đăng ký nào được đối xử theo cách đặc biệt. Hãy nhớ rằng những chi tiết này là từ bộ nhớ, chúng có thể không hoàn toàn chính xác.
Ví dụ: nếu R3 là PC, R4 là địa chỉ cuộc gọi SCRT, R5 là địa chỉ trả về SCRT và R2 là "ngăn xếp" (dấu ngoặc kép khi nó được triển khai trong phần mềm), SEP R4
sẽ đặt R4 là PC và bắt đầu chạy SCRT mã cuộc gọi.
Sau đó, nó sẽ lưu trữ R3 trên "ngăn xếp" R2 (tôi nghĩ R6 đã được sử dụng để lưu trữ tạm thời), điều chỉnh nó lên hoặc xuống, lấy hai byte theo sau R3, tải chúng vào R3, sau đó thực hiện SEP R3
và chạy ở địa chỉ mới.
Để quay lại, nó sẽ SEP R5
kéo địa chỉ cũ ra khỏi ngăn xếp R2, thêm hai vào đó (để bỏ qua byte địa chỉ của cuộc gọi), tải nó vào R3 và SEP R3
bắt đầu chạy mã trước đó.
Rất khó để quấn lấy đầu bạn lúc đầu sau tất cả mã dựa trên ngăn xếp 6502/6809 / z80 nhưng vẫn trang nhã theo cách đập đầu vào tường. Ngoài ra, một trong những tính năng bán chạy nhất của chip là một bộ đầy đủ gồm 16 thanh ghi 16-bit, mặc dù thực tế là bạn đã ngay lập tức mất 7 trong số đó (5 cho SCRT, hai cho DMA và ngắt từ bộ nhớ). Ahh, thành công của tiếp thị so với thực tế :-)
Hệ thống z thực sự khá giống nhau, sử dụng các thanh ghi R14 và R15 của nó để gọi / trả.
Trong C ++ (có thể thích ứng với C) stack.cc :
static int
find_stack_direction ()
{
static char *addr = 0;
auto char dummy;
if (addr == 0)
{
addr = &dummy;
return find_stack_direction ();
}
else
{
return ((&dummy > addr) ? 1 : -1);
}
}
static
cho việc này. Thay vào đó, bạn có thể chuyển địa chỉ dưới dạng đối số cho một cuộc gọi đệ quy.
static
, nếu bạn gọi này nhiều hơn một lần, những cuộc gọi tiếp theo có thể thất bại ...
Lợi thế của việc giảm dần là trong các hệ thống cũ, ngăn xếp thường ở trên cùng của bộ nhớ. Các chương trình thường lấp đầy bộ nhớ bắt đầu từ dưới cùng, do đó, kiểu quản lý bộ nhớ này giảm thiểu nhu cầu đo lường và đặt dưới cùng của ngăn xếp ở nơi nào đó hợp lý.
Trong MIPS và nhiều kiến trúc RISC hiện đại (như PowerPC, RISC-V, SPARC ...) không có push
và pop
hướng dẫn. Những hoạt động đó được thực hiện một cách rõ ràng bằng cách điều chỉnh thủ công con trỏ ngăn xếp, sau đó tải / lưu trữ giá trị tương đối vào con trỏ đã điều chỉnh. Tất cả các thanh ghi (ngoại trừ thanh ghi 0) đều có mục đích chung vì vậy về lý thuyết bất kỳ thanh ghi nào cũng có thể là con trỏ ngăn xếp và ngăn xếp có thể phát triển theo bất kỳ hướng nào mà người lập trình muốn
Điều đó nói rằng, ngăn xếp thường phát triển xuống trên hầu hết các kiến trúc, có thể để tránh trường hợp khi dữ liệu ngăn xếp và chương trình hoặc dữ liệu đống lớn lên và xung đột với nhau. Ngoài ra còn có những lý do giải quyết tuyệt vời được đề cập đến câu trả lời của sh- . Một số ví dụ: MIPS ABIs phát triển xuống dưới và sử dụng $29
(AKA $sp
) làm con trỏ ngăn xếp, RISC-V ABI cũng phát triển xuống dưới và sử dụng x2 làm con trỏ ngăn xếp
Trong Intel 8051, ngăn xếp tăng lên, có thể là do không gian bộ nhớ quá nhỏ (128 byte trong phiên bản gốc) nên không có heap và bạn không cần đặt ngăn xếp lên trên để nó được tách ra khỏi heap ngày càng tăng. từ đáy
Bạn có thể tìm thêm thông tin về việc sử dụng ngăn xếp trong các kiến trúc khác nhau tại https://en.wikipedia.org/wiki/Calling_convention
Xem thêm
Chỉ là một bổ sung nhỏ cho các câu trả lời khác, theo như tôi thấy thì chưa chạm đến điểm này:
Việc ngăn xếp phát triển xuống dưới làm cho tất cả các địa chỉ trong ngăn xếp có độ lệch dương so với con trỏ ngăn xếp. Không cần hiệu số âm, vì chúng chỉ trỏ đến không gian ngăn xếp không sử dụng. Điều này giúp đơn giản hóa việc truy cập các vị trí ngăn xếp khi bộ xử lý hỗ trợ định địa chỉ tương đối điểm xếp chồng.
Nhiều bộ xử lý có các hướng dẫn cho phép truy cập có giá trị bù chỉ dương so với một số thanh ghi. Chúng bao gồm nhiều kiến trúc hiện đại, cũng như một số kiến trúc cũ. Ví dụ: ARM Thumb ABI cung cấp cho các truy cập tương đối với điểm xếp chồng với độ lệch dương được mã hóa trong một từ lệnh 16 bit.
Nếu ngăn xếp lớn dần lên, tất cả các hiệu số hữu ích liên quan đến điểm xếp chồng sẽ là số âm, điều này kém trực quan và kém thuận tiện hơn. Nó cũng mâu thuẫn với các ứng dụng khác của địa chỉ liên quan đến thanh ghi, ví dụ để truy cập các trường của một cấu trúc.
Trên hầu hết các hệ thống, ngăn xếp tăng dần và bài viết của tôi tại https://gist.github.com/cpq/8598782 giải thích TẠI SAO nó lại giảm. Nó rất đơn giản: làm thế nào để bố trí hai khối bộ nhớ đang phát triển (heap và stack) trong một đoạn bộ nhớ cố định? Giải pháp tốt nhất là đặt chúng ở hai đầu đối diện và để chúng phát triển về phía nhau.
Nó phát triển xuống vì bộ nhớ được cấp cho chương trình có "dữ liệu vĩnh viễn" tức là mã cho chính chương trình ở dưới cùng, sau đó là đống ở giữa. Bạn cần một điểm cố định khác mà từ đó tham chiếu ngăn xếp, để bạn đứng đầu. Điều này có nghĩa là ngăn xếp tăng dần xuống, cho đến khi nó có khả năng tiếp giáp với các đối tượng trên đống.
Macro này sẽ phát hiện nó trong thời gian chạy mà không có UB:
#define stk_grows_up_eh() stk_grows_up__(&(char){0})
_Bool stk_grows_up__(char *ParentsLocal);
__attribute((__noinline__))
_Bool stk_grows_up__(char *ParentsLocal) {
return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
}