Tuổi thọ của một biến tĩnh trong hàm C ++ là bao nhiêu?


373

Nếu một biến được khai báo như statictrong phạm vi của hàm thì nó chỉ được khởi tạo một lần và giữ lại giá trị của nó giữa các lệnh gọi hàm. Chính xác thì cuộc đời của nó là gì? Khi nào hàm tạo và hàm hủy của nó được gọi?

void foo() 
{ 
    static string plonk = "When will I die?";
}

Câu trả lời:


257

Thời gian tồn tại của các staticbiến hàm bắt đầu lần đầu tiên [0] luồng chương trình gặp phải khai báo và nó kết thúc khi kết thúc chương trình. Điều này có nghĩa là thời gian chạy phải thực hiện một số lưu giữ sách để phá hủy nó chỉ khi nó thực sự được xây dựng.

Ngoài ra, do tiêu chuẩn nói rằng các bộ phá hủy của các đối tượng tĩnh phải chạy theo thứ tự ngược lại khi hoàn thành công trình của chúng [1] và thứ tự xây dựng có thể phụ thuộc vào chương trình cụ thể, nên phải tính đến thứ tự xây dựng .

Thí dụ

struct emitter {
    string str;
    emitter(const string& s) : str(s) { cout << "Created " << str << endl; }
    ~emitter() { cout << "Destroyed " << str << endl; }
};

void foo(bool skip_first) 
{
    if (!skip_first)
        static emitter a("in if");
    static emitter b("in foo");
}

int main(int argc, char*[])
{
    foo(argc != 2);
    if (argc == 3)
        foo(false);
}

Đầu ra:

C:> sample.exe
Được tạo trong foo
Bị phá hủy trong foo

C:> sample.exe 1
Được tạo trong if
Được tạo trong foo
Bị phá hủy trong foo
Bị phá hủy trong if

C:> sample.exe 1 2
Tạo trong foo
Được tạo trong nếu
Bị hủy trong nếu
Bị hủy trong foo

[0]C ++ 98 [2] không có tham chiếu đến nhiều luồng nên điều này sẽ hoạt động như thế nào trong môi trường đa luồng là không xác định và có thể gây rắc rối như Roddy đề cập.

[1] C ++ 98 phần 3.6.3.1 [basic.start.term]

[2]Trong C ++ 11 statics được khởi tạo theo cách an toàn cho chủ đề, điều này còn được gọi là Magic Statics .


2
Đối với các loại đơn giản không có tác dụng phụ của c'tor / d'tor, việc tối ưu hóa chúng theo cách tương tự như các loại đơn giản toàn cầu là một cách đơn giản. Điều này tránh sự phân nhánh, cờ và các vấn đề phá hủy. Điều đó không có nghĩa là cuộc sống của họ là khác nhau.
John McFarlane

1
Nếu hàm có thể được gọi bởi nhiều luồng, thì điều này có nghĩa là bạn cần đảm bảo rằng các khai báo tĩnh phải được bảo vệ bởi một mutex trong C ++ 98 ??
mã đồng minh

1
"Kẻ hủy diệt" của các đối tượng toàn cầu phải chạy theo thứ tự ngược lại khi hoàn thành công trình của chúng "không áp dụng ở đây, vì các đối tượng này không phải là toàn cầu. Thứ tự phá hủy của người dân địa phương với thời gian lưu trữ tĩnh hoặc luồng phức tạp hơn đáng kể so với LIFO thuần túy, xem phần 3.6.3[basic.start.term]
Ben Voigt

2
Cụm từ "chấm dứt chương trình" không đúng. Điều gì về statics trong Windows dlls được tải và tải động? Rõ ràng là tiêu chuẩn C ++ hoàn toàn không đối phó với các hội đồng (sẽ tốt hơn nếu có), nhưng một sự làm rõ xung quanh chính xác những gì tiêu chuẩn nói ở đây sẽ tốt. Nếu bao gồm cụm từ "khi kết thúc chương trình", về mặt kỹ thuật, nó sẽ làm cho bất kỳ triển khai C ++ nào với các hội đồng không tải động không tuân thủ.
Roger Sanders

2
@Motti Tôi không tin rằng tiêu chuẩn này cho phép rõ ràng các thư viện động, nhưng cho đến bây giờ tôi cũng không tin có bất cứ điều gì cụ thể trong tiêu chuẩn mâu thuẫn với việc triển khai. Tất nhiên, nói đúng ngôn ngữ ở đây không nói rằng các đối tượng tĩnh không thể bị phá hủy sớm hơn thông qua các phương tiện khác, chỉ là chúng phải bị hủy khi quay trở lại từ chính hoặc gọi std :: exit. Một dòng khá tốt mặc dù tôi nghĩ.
Roger Sanders

125

Motti nói đúng về trật tự, nhưng có một số điều khác cần xem xét:

Trình biên dịch thường sử dụng biến cờ ẩn để cho biết liệu trạng thái cục bộ đã được khởi tạo chưa và cờ này được kiểm tra trên mỗi mục nhập của hàm. Rõ ràng đây là một thành tích nhỏ, nhưng điều đáng quan tâm hơn là cờ này không được đảm bảo an toàn cho chuỗi.

Nếu bạn có một tĩnh cục bộ như trên và foođược gọi từ nhiều luồng, bạn có thể có các điều kiện chủng tộc gây ra plonkđược khởi tạo không chính xác hoặc thậm chí nhiều lần. Ngoài ra, trong trường hợp nàyplonk có thể bị phá hủy bởi một luồng khác với luồng đã xây dựng nó.

Bất chấp những gì tiêu chuẩn nói, tôi rất cảnh giác với trật tự phá hủy tĩnh cục bộ thực sự, bởi vì có thể bạn vô tình dựa vào một tĩnh vẫn có hiệu lực sau khi nó bị phá hủy, và điều này thực sự rất khó để theo dõi.


68
C ++ 0x yêu cầu khởi tạo tĩnh phải là luồng an toàn. Vì vậy, hãy cảnh giác nhưng mọi thứ sẽ chỉ trở nên tốt hơn.
deft_code

Vấn đề trật tự phá hủy có thể tránh được với một chính sách nhỏ. các đối tượng tĩnh / toàn cầu (singletons, v.v.) sẽ không truy cập các đối tượng tĩnh khác trong các thân phương thức của chúng. Chúng chỉ được truy cập trong các hàm tạo trong đó một tham chiếu / con trỏ có thể được lưu trữ để truy cập sau này trong các phương thức. Điều này là không hoàn hảo nhưng nên khắc phục 99 trường hợp và trường hợp nó không bắt được rõ ràng là tanh và nên được xem xét trong mã đánh giá. Đây vẫn chưa phải là một bản sửa lỗi hoàn hảo vì chính sách không thể được thi hành bằng ngôn ngữ
deft_code

Tôi là một chút của một người mới, nhưng tại sao chính sách này không thể được thi hành bằng ngôn ngữ?
cjcurrie

9
Kể từ C ++ 11, đây không còn là vấn đề nữa. Câu trả lời của Motti được cập nhật theo đó.
Nilanjan Basu

10

Các giải thích hiện tại không thực sự hoàn chỉnh nếu không có quy tắc thực tế từ Tiêu chuẩn, được tìm thấy trong 6.7:

Việc khởi tạo bằng không của tất cả các biến phạm vi khối với thời lượng lưu trữ tĩnh hoặc thời gian lưu trữ luồng được thực hiện trước khi bất kỳ khởi tạo nào khác diễn ra. Khởi tạo liên tục của một thực thể phạm vi khối với thời gian lưu trữ tĩnh, nếu có thể, được thực hiện trước khi khối đầu tiên được nhập. Việc triển khai được phép thực hiện khởi tạo sớm các biến phạm vi khối khác với thời lượng lưu trữ tĩnh hoặc luồng trong cùng điều kiện triển khai được phép khởi tạo tĩnh một biến với thời lượng lưu trữ tĩnh hoặc luồng trong phạm vi không gian tên. Mặt khác, một biến như vậy được khởi tạo khi điều khiển lần đầu tiên đi qua khai báo của nó; một biến như vậy được coi là khởi tạo khi hoàn thành khởi tạo. Nếu khởi tạo thoát bằng cách ném một ngoại lệ, việc khởi tạo chưa hoàn tất, vì vậy nó sẽ được thử lại lần sau khi điều khiển tiếp theo đi vào khai báo. Nếu điều khiển đi vào khai báo đồng thời trong khi biến đang được khởi tạo, thì việc thực thi đồng thời sẽ chờ hoàn thành việc khởi tạo. Nếu điều khiển nhập lại khai báo một cách đệ quy trong khi biến đang được khởi tạo, hành vi không được xác định.


8

FWIW, Codegear C ++ Builder không phá hủy theo thứ tự dự kiến ​​theo tiêu chuẩn.

C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if

... đó là một lý do khác để không dựa vào lệnh hủy diệt!


57
Không phải là một cuộc tranh luận tốt. Tôi muốn nói rằng đây là một đối số không sử dụng trình biên dịch này.
Martin York

26
Hừm. Nếu bạn quan tâm đến việc sản xuất mã di động trong thế giới thực, thay vì chỉ là mã di động về mặt lý thuyết, tôi nghĩ sẽ hữu ích khi biết những lĩnh vực nào của ngôn ngữ có thể gây ra vấn đề. Tôi sẽ ngạc nhiên nếu C ++ Builder là duy nhất trong việc không xử lý việc này.
Roddy

17
Tôi đồng ý, ngoại trừ việc tôi diễn đạt nó là "trình biên dịch nào gây ra sự cố và khu vực ngôn ngữ nào họ thực hiện" ;-P
Steve Jessop

0

Các biến tĩnh được sử dụng khi quá trình thực thi chương trình bắt đầu và nó vẫn khả dụng cho đến khi việc thực hiện chương trình kết thúc.

Các biến tĩnh được tạo trong Phân đoạn dữ liệu của bộ nhớ .


điều này không đúng với các biến ở phạm vi chức năng
đánh thức
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.