Khi nào các biến tĩnh cấp hàm được cấp phát / khởi tạo?


89

Tôi khá tin tưởng rằng các biến được khai báo trên toàn cầu sẽ được cấp phát (và khởi tạo, nếu có) tại thời điểm bắt đầu chương trình.

int globalgarbage;
unsigned int anumber = 42;

Nhưng những gì về tĩnh được định nghĩa trong một hàm?

void doSomething()
{
  static bool globalish = true;
  // ...
}

Không gian globalishđược cấp phát khi nào? Tôi đoán khi chương trình bắt đầu. Nhưng liệu nó có được khởi tạo sau đó không? Hay nó được khởi tạo khi doSomething()lần đầu tiên được gọi?

Câu trả lời:


91

Tôi tò mò về điều này vì vậy tôi đã viết chương trình thử nghiệm sau và biên dịch nó với g ++ phiên bản 4.1.2.

include <iostream>
#include <string>

using namespace std;

class test
{
public:
        test(const char *name)
                : _name(name)
        {
                cout << _name << " created" << endl;
        }

        ~test()
        {
                cout << _name << " destroyed" << endl;
        }

        string _name;
};

test t("global variable");

void f()
{
        static test t("static variable");

        test t2("Local variable");

        cout << "Function executed" << endl;
}


int main()
{
        test t("local to main");

        cout << "Program start" << endl;

        f();

        cout << "Program end" << endl;
        return 0;
}

Kết quả không như tôi mong đợi. Hàm khởi tạo cho đối tượng tĩnh không được gọi cho đến lần đầu tiên hàm được gọi. Đây là đầu ra:

global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed

30
Để giải thích rõ: biến static được khởi tạo lần đầu tiên khi thực thi tiếp cận với khai báo của nó, không phải khi hàm chứa được gọi. Nếu bạn chỉ có một static ở đầu hàm (ví dụ: trong ví dụ của bạn) thì chúng giống nhau, nhưng không nhất thiết: ví dụ: if you have 'if (...) {static MyClass x; ...} ', thì' x 'sẽ không được khởi tạo TẤT CẢ trong lần thực thi đầu tiên của hàm đó trong trường hợp điều kiện của câu lệnh if đánh giá là sai.
EvanED

4
Nhưng điều này không dẫn đến chi phí thời gian chạy, vì mỗi lần biến static được sử dụng, chương trình phải kiểm tra xem nó đã được sử dụng trước đó chưa, vì nếu chưa, nó phải được khởi tạo? Trong trường hợp đó, kiểu đó hơi tệ một chút.
HelloGoodbye

hoàn hảo minh họa
Des1gnWizard

@veio: Có, quá trình khởi tạo là an toàn theo chuỗi. Xem câu hỏi đó để biết thêm chi tiết: stackoverflow.com/questions/23829389/…
Rémi

2
@HelloGoodbye: vâng, nó dẫn đến chi phí thời gian chạy. Cũng xem câu hỏi đó: stackoverflow.com/questions/23829389/…
Rémi

53

Một số mô tả liên quan từ C ++ Standard:

3.6.2 Khởi tạo các đối tượng không phải cục bộ [basic.start.init]

1

Việc lưu trữ các đối tượng có thời lượng lưu trữ tĩnh ( basic.stc.static ) sẽ được khởi tạo bằng 0 ( dcl.init ) trước khi bất kỳ quá trình khởi tạo nào khác diễn ra. Các đối tượng thuộc loại POD ( basic.types ) có thời lượng lưu trữ tĩnh được khởi tạo bằng biểu thức hằng số ( expr.const ) sẽ được khởi tạo trước khi bất kỳ quá trình khởi tạo động nào diễn ra. Các đối tượng của phạm vi không gian tên có thời lượng lưu trữ tĩnh được xác định trong cùng một đơn vị dịch và được khởi tạo động sẽ được khởi tạo theo thứ tự mà định nghĩa của chúng xuất hiện trong đơn vị dịch. [Lưu ý: dcl.init.aggr mô tả thứ tự mà các thành viên tổng hợp được khởi tạo. Việc khởi tạo các đối tượng tĩnh cục bộ được mô tả trong stmt.dcl . ]

[thêm văn bản bên dưới thêm nhiều quyền tự do hơn cho người viết trình biên dịch]

6.7 Câu lệnh khai báo [stmt.dcl]

...

4

Khởi tạo bằng không ( dcl.init ) của tất cả các đối tượng cục bộ có thời lượng lưu trữ tĩnh ( basic.stc.static ) được thực hiện trước khi bất kỳ quá trình khởi tạo nào khác diễn ra. Một đối tượng cục bộ kiểu POD ( basic.types ) với thời lượng lưu trữ tĩnh được khởi tạo bằng biểu thức hằng được khởi tạo trước khi khối của nó được nhập lần đầu tiên. Một triển khai được phép thực hiện khởi tạo sớm các đối tượng cục bộ khác với thời lượng lưu trữ tĩnh trong cùng điều kiện mà một triển khai được phép khởi tạo tĩnh một đối tượng có thời hạn lưu trữ tĩnh trong phạm vi không gian tên ( basic.start.init). Nếu không, một đối tượng như vậy được khởi tạo lần đầu tiên điều khiển đi qua khai báo của nó; một đối tượng như vậy được coi là đã khởi tạo sau khi hoàn thành quá trình khởi tạo. Nếu quá trình khởi tạo thoát ra bằng cách ném một ngoại lệ, thì quá trình khởi tạo chưa hoàn tất, vì vậy nó sẽ được thử lại vào lần tiếp theo điều khiển đi vào khai báo. Nếu điều khiển nhập lại khai báo (đệ quy) trong khi đối tượng đang được khởi tạo, thì hành vi là không xác định. [ Ví dụ:

      int foo(int i)
      {
          static int s = foo(2*i);  // recursive call - undefined
          return i+1;
      }

- cuối ví dụ ]

5

Hàm hủy cho một đối tượng cục bộ có thời lượng lưu trữ tĩnh sẽ được thực thi nếu và chỉ khi biến được tạo. [Lưu ý: basic.start.term mô tả thứ tự mà các đối tượng cục bộ có thời lượng lưu trữ tĩnh bị phá hủy. ]


Điều này đã trả lời câu hỏi của tôi và không dựa trên "bằng chứng giai thoại" không giống như câu trả lời được chấp nhận. Tôi đã đặc biệt tìm kiếm đề cập này về các ngoại lệ trong phương thức khởi tạo của các đối tượng tĩnh cục bộ của hàm được khởi tạo tĩnh:If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
Bensge

26

Bộ nhớ cho tất cả các biến tĩnh được cấp phát khi tải chương trình. Nhưng các biến tĩnh cục bộ được tạo và khởi tạo lần đầu tiên chúng được sử dụng, không phải lúc khởi động chương trình. Có một số bài đọc tốt về điều đó và các thông tin tĩnh nói chung, ở đây . Nói chung, tôi nghĩ một số vấn đề này phụ thuộc vào việc triển khai, đặc biệt nếu bạn muốn biết vị trí của thứ này trong bộ nhớ.


2
không hoàn toàn, các tĩnh cục bộ được cấp phát và khởi tạo bằng không "lúc tải chương trình" (trong dấu ngoặc kép, vì điều đó cũng không đúng), và sau đó được khởi động lại lần đầu tiên khi nhập hàm chúng đang ở trong đó.
Mooing Duck

Hình như mối liên kết đó giờ đã đứt, 7 năm sau.
Steve

1
Đúng, liên kết đã bị hỏng. Đây là kho lưu trữ: web.archive.org/web/20100328062506/http://www.acm.org/…
Eugene

10

Trình biên dịch sẽ cấp phát (các) biến tĩnh được xác định trong một hàm fookhi tải chương trình, tuy nhiên trình biên dịch cũng sẽ thêm một số lệnh bổ sung (mã máy) vào hàm của bạn foođể lần đầu tiên nó được gọi ra, mã bổ sung này sẽ khởi tạo biến tĩnh ( ví dụ: gọi hàm tạo, nếu có).

@Adam: Điều này đằng sau việc chèn mã bởi trình biên dịch là lý do cho kết quả bạn thấy.


5

Tôi thử kiểm tra lại mã từ Adam Pierce và thêm hai trường hợp nữa: biến tĩnh trong lớp và kiểu POD. Trình biên dịch của tôi là g ++ 4.8.1, trong Windows OS (MinGW-32). Kết quả là biến tĩnh trong lớp được xử lý giống với biến toàn cục. Hàm tạo của nó sẽ được gọi trước khi nhập hàm main.

  • Kết luận (dành cho g ++, môi trường Windows):

    1. Biến toàn cụcthành viên tĩnh trong lớp : hàm tạo được gọi trước khi nhập hàm main (1) .
    2. Biến tĩnh cục bộ : hàm tạo chỉ được gọi khi việc thực thi đạt đến khai báo của nó ở lần đầu tiên.
    3. Nếu biến tĩnh cục bộ là kiểu POD , thì nó cũng được khởi tạo trước khi nhập hàm main (1) . Ví dụ cho kiểu POD: static int number = 10;

(1) : Trạng thái đúng phải là: "trước khi bất kỳ hàm nào từ cùng một đơn vị dịch được gọi". Tuy nhiên, để đơn giản, như ví dụ dưới đây, thì nó là chức năng chính .

bao gồm <iostream>

#include < string>

using namespace std;

class test
{
public:
   test(const char *name)
            : _name(name)
    {
            cout << _name << " created" << endl;
    }

    ~test()
    {
            cout << _name << " destroyed" << endl;
    }

    string _name;
    static test t; // static member
 };
test test::t("static in class");

test t("global variable");

void f()
{
    static  test t("static variable");
    static int num = 10 ; // POD type, init before enter main function

    test t2("Local variable");
    cout << "Function executed" << endl;
}

int main()
{
    test t("local to main");
    cout << "Program start" << endl;
    f();
    cout << "Program end" << endl;
    return 0;
 }

kết quả:

static in class created
global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
static in class destroyed

Có ai đã thử nghiệm trong Linux env không?


3

Các biến tĩnh được cấp phát bên trong một đoạn mã - chúng là một phần của hình ảnh thực thi và do đó được ánh xạ trong đã được khởi tạo.

Các biến tĩnh trong phạm vi hàm được xử lý giống nhau, phạm vi hoàn toàn là một cấu trúc cấp ngôn ngữ.

Vì lý do này, bạn được đảm bảo rằng một biến tĩnh sẽ được khởi tạo bằng 0 (trừ khi bạn chỉ định một cái gì đó khác) thay vì một giá trị không xác định.

Có một số khía cạnh khác để khởi tạo mà bạn có thể tận dụng - ví dụ: các phân đoạn được chia sẻ cho phép các phiên bản thực thi khác nhau của bạn chạy cùng một lúc để truy cập cùng một biến tĩnh.

Trong C ++ (phạm vi toàn cầu), các đối tượng tĩnh có các hàm tạo của chúng được gọi như một phần của chương trình khởi động, dưới sự kiểm soát của thư viện thời gian chạy C. Trong Visual C ++, ít nhất thứ tự mà các đối tượng được khởi tạo có thể được kiểm soát bởi init_seg pragma.


4
Câu hỏi này là về tĩnh trong phạm vi chức năng. Ít nhất là khi chúng có các hàm tạo không tầm thường, chúng được khởi tạo trong lần nhập đầu tiên vào hàm. Hay cụ thể hơn là khi đạt được dòng đó.
Adam Mitz

Đúng - nhưng câu hỏi nói về không gian được cấp cho biến và sử dụng các kiểu dữ liệu đơn giản. Không gian vẫn được phân bổ trong phân đoạn mã
Rob Walker

Tôi không thấy phân đoạn mã so với phân đoạn dữ liệu thực sự quan trọng như thế nào ở đây. Tôi nghĩ chúng tôi cần OP làm rõ. Anh ấy đã nói "và khởi tạo, nếu có".
Adam Mitz

5
các biến không bao giờ được cấp phát bên trong đoạn mã; theo cách này họ sẽ không thể viết được.
botismarius 12-08

1
biến tĩnh được cấp phát không gian trong phân đoạn dữ liệu hoặc trong phân đoạn bss tùy thuộc vào việc chúng có được khởi tạo hay không.
EmptyData

3

Hay nó được khởi tạo khi doSomething () được gọi lần đầu?

Vâng, đúng vậy. Điều này, trong số những thứ khác, cho phép bạn khởi tạo cấu trúc dữ liệu được truy cập toàn cầu khi thích hợp, ví dụ như bên trong các khối try / catch. Vd: thay vì

int foo = init(); // bad if init() throws something

int main() {
  try {
    ...
  }
  catch(...){
    ...
  }
}

bạn có thể viết

int& foo() {
  static int myfoo = init();
  return myfoo;
}

và sử dụng nó bên trong khối try / catch. Trong lần gọi đầu tiên, biến sẽ được khởi tạo. Sau đó, trong lần gọi đầu tiên và tiếp theo, giá trị của nó sẽ được trả về (bằng cách tham chiếu).

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.