Phạm vi
Biến khai báo trong phạm vi của một cặp { }
là trên stack. Điều này áp dụng cho các biến được khai báo ở đầu hàm hoặc trong bất kỳ cặp nào { }
trong hàm.
int myfunc()
{
int i = 0; // On the stack, scoped: myfunc
printf("%i\n");
if (1)
{
int j = 1; // On the stack, scope: this if statement
printf("%i %i\n",i,j);
}
printf("%i %i\n",i,j); // Won't work, no j
}
Những ngày này phạm vi của các biến được giới hạn ở xung quanh { }
. Tôi nhớ lại rằng một số trình biên dịch Microsoft cũ hơn không giới hạn phạm vi, và trong ví dụ trên, printf()
cuối cùng sẽ biên dịch.
Vậy nó ở đâu trong bộ nhớ?
Bộ nhớ i
và j
chỉ được đặt trước trên ngăn xếp. Điều này không giống như phân bổ bộ nhớ được thực hiện với malloc()
. Điều đó rất quan trọng, bởi vì gọi số malloc()
rất chậm so sánh. Ngoài ra với bộ nhớ được phân bổ động bằng cách sử dụng malloc()
bạn phải gọi free()
.
Có hiệu lực trình biên dịch biết trước thời gian cần thiết cho các biến của hàm và sẽ tạo mã liên quan đến bộ nhớ liên quan đến bất kỳ con trỏ ngăn xếp nào khi được gọi là myfunc()
. Vì vậy, miễn là ngăn xếp là đủ lớn (2MBytes bình thường, phụ thuộc vào hệ điều hành), tất cả là tốt.
Tràn ngăn xảy ra trong trường hợp myfunc()
được gọi với con trỏ ngăn xếp đã gần cuối ngăn xếp (tức là myfunc()
được gọi bởi một hàm mà người khác gọi là , vv Mỗi lớp của các cuộc gọi lồng nhau đến các hàm di chuyển con trỏ ngăn xếp nhiều hơn một chút và chỉ được chuyển trở lại khi các hàm trả về).
Nếu khoảng cách giữa ngăn xếp ngăn xếp và cuối ngăn xếp không đủ lớn để giữ tất cả các biến được khai báo trong myfunc()
, mã cho myfunc()
sẽ chỉ đơn giản cố gắng sử dụng các vị trí ngoài cuối ngăn xếp. Điều đó gần như luôn luôn là một điều xấu, và chính xác như thế nào xấu và khó khăn như thế nào để nhận thấy rằng một cái gì đó đã đi sai phụ thuộc vào hệ điều hành. Trên bộ điều khiển nhúng nhỏ, nó có thể là một cơn ác mộng vì nó thường có nghĩa là một phần khác của dữ liệu của chương trình (ví dụ như biến toàn cục) bị ghi đè âm thầm và có thể rất khó gỡ lỗi. Trên các hệ thống lớn hơn (Linux, Windows), hệ điều hành sẽ cho bạn biết những gì đã xảy ra hoặc sẽ chỉ làm cho ngăn xếp lớn hơn.
cân nhắc Runtime Hiệu quả
Trong ví dụ trên tôi gán giá trị cho i
và j
. Điều này thực sự mất một số lượng nhỏ thời gian chạy. j
được chỉ định 1 sau khi đánh giá tuyên bố if và chi nhánh tiếp theo vào nơi j
được khai báo.
Nói ví dụ câu lệnh if đã không được đánh giá là đúng; trong trường hợp đó j
không bao giờ được gán 1. Nếu j
được khai báo ở đầu myfunc()
thì nó sẽ luôn được gán giá trị là 1 bất kể câu lệnh if có đúng hay không - một phần nhỏ thời gian. Nhưng hãy xem xét một ví dụ ít tầm thường hơn khi một mảng lớn được khai báo là đã khởi tạo; sẽ mất nhiều thời gian hơn.
int myfunc()
{
int i = 0; // On the stack, scoped: myfunc
int k[10000] = {0} // On the stack, scoped: myfunc. A complete waste of time
// when the if statement evaluates to false.
printf("%i\n");
if (0)
{
int j = 1; // On the stack, scope: this if statement
// It would be better to move the declaration of k to here
// so that it is initialised only when the if evaluates to true.
printf("%i %i %i\n",i,j,k[500]);
}
printf("%i %i\n",i,j); // Won't work, no j
}
Đặt tuyên bố k
ở đầu myfunc()
có nghĩa là một vòng lặp 10.000 dài được thực hiện để khởi k mỗi lần myfunc()
được gọi. Tuy nhiên nó không bao giờ được sử dụng, vì vậy vòng lặp đó là một sự lãng phí thời gian hoàn toàn. Tất nhiên, trong những trình biên dịch ví dụ tầm thường này sẽ tối ưu hóa mã không cần thiết, vv. Trong mã thực mà trình biên dịch không thể dự đoán trước thời gian thực hiện dòng chảy thì mọi thứ sẽ được đặt đúng vị trí.
Tại sao không xem mã trình biên dịch của bạn tạo ra để tìm hiểu? –
@CarlNorum có thể vì không phải ai cũng biết lắp ráp? – Kolyunya
Trong trường hợp này đó không thực sự là một câu trả lời hay. Bạn sẽ phải học nếu bạn muốn biết những gì đang xảy ra. Hành vi ở đây không thể được xác định bằng cách đoán dựa trên một tiêu chuẩn ngôn ngữ, vì ngôn ngữ không chỉ định nó đủ tốt.Ít nhất, phá vỡ ABI và/hoặc tiêu chuẩn gọi thủ tục là cần thiết. –