2016-10-18 15 views
7

Cho một biến thành viên tĩnh mà được khởi tạo từ một biến thành viên tĩnh của khác lớp, phi nghĩa đen struct ii đôi khi được mặc định khởi tạo 0hay để 333. Điều này phụ thuộc vào trình biên dịch hoặc thứ tự liên kết. Mã giả để chứng minh:C++: tĩnh trên tĩnh biến thành viên khởi tạo phụ thuộc với int vs struct

class StaticClass: // file 'ONE.cpp/.hpp' 
    static int i = 2 
    static struct ii { int a = 333 } 

class ABC: // file 'abc.cpp' 
    static int abc_i = StaticClass::i // always 2 
    static struct abc_ii = StaticClass::ii // sometimes 0, sometimes 333 

Calling g++ -std=c++11 abc.cpp ONE.cpp && ./a.out kết quả trong i = 2/ii = 0 (gcc 4.8.1, cùng với kêu vang ++ 3.7; -Wall -Wextra không bao giờ phàn nàn).

Nhưng gọi g++ -std=c++11 ONE.cpp abc.cpp && ./a.out kết quả bằng i = 2/ii = 333!

Các tương tự xảy ra với ONE.o abc.o vs abc.o ONE.o và cũng có khi concatenating các tập tin một cách này hay cách khác:

cat ONE.cpp abc.cpp > X.cpp && g++ X.cpp && ./a.out vs cat abc.cpp ONE.cpp > Y.cpp && g++ Y.cpp && ./a.out

Loại bỏ bao gồm và di chuyển mã xung quanh trong file duy nhất, khởi tạo mặc định để 0 xảy ra khi đơn hàng này xuất hiện:

const OneI ABC::def_ii = StaticClass::ii; const OneI StaticClass::ii = OneI{333};

và một đến 333 với trật tự này:

const OneI StaticClass::ii = OneI{333}; const OneI ABC::def_ii = StaticClass::ii;

Tại sao điều này thậm chí còn xảy ra với hai đơn vị biên soạn riêng biệt? Điều này có thể tránh được bằng cách nào đó bằng cách thực thi thứ tự sau này không? Đang sử dụng con trỏ tĩnh trong ABC đến StaticClass::ii an toàn (tôi không muốn, mặc dù)?

Full C++:


/* File: abc.cpp */ 

#include <iostream> 
#include "ONE.hpp" 

struct ABC { 
    ABC(); 

    static const int def_i; 
    static const OneI def_ii; 
    void arg_i(const int &x) { std::cout << "i = " << x << " ";}; 
    void arg_ii(const OneI &x) { std::cout << "/ ii = " << x.a << " ";}; 

}; 

ABC::ABC() { 
    arg_i(def_i); 
    arg_ii(def_ii); 
} 

const int ABC::def_i = StaticClass::i; 
const OneI ABC::def_ii = StaticClass::ii; 

int main() { 
    ABC a; 
    std::cout << '\n'; 
} 
/* End: abc.cpp */ 

/* File: ONE.cpp */ 

#include <iostream> 

#include "ONE.hpp" 

const int StaticClass::i = 2; 
const OneI StaticClass::ii = OneI{333}; 

/* End: ONE.cpp */ 

/* File: ONE.hpp */ 

#include <iostream> 

#ifndef One 
#define One 

struct OneI { 
    OneI(int a_) : a(a_) { } 
    int a; 
}; 

struct StaticClass { 
    const static int i; 
    const static OneI ii; 
}; 

#endif // One header guard 

/* End: ONE.hpp */ 

Trả lời

4

Xin chúc mừng! Bạn đã gặp phải static initialization order fiasco.

Thứ tự khởi tạo của các đối tượng tĩnh không được xác định trên nhiều đơn vị dịch.

StaticClass::ii được xác định trong ONE.cppABC::def_ii được xác định trong abc.cpp. Do đó StaticClass::ii có thể hoặc không được khởi tạo trước ABC::def_ii. Kể từ khi khởi tạo ABC::def_ii sử dụng giá trị StaticClass::ii giá trị sẽ phụ thuộc vào việc StaticClass::ii đã được khởi tạo chưa .

Thứ tự khởi tạo của các đối tượng tĩnh trong phạm vi đơn vị dịch được xác định. Các đối tượng được khởi tạo theo thứ tự mà chúng được định nghĩa.Do đó khi bạn nối các tệp nguồn, thứ tự khởi tạo được xác định. Tuy nhiên, khi bạn nối các file theo thứ tự sai, thứ tự khởi tạo được xác định là sai:

const OneI ABC::def_ii = StaticClass::ii; // StaticClass::ii wasn't initialized yet 
const OneI StaticClass::ii = OneI{333}; 

này có thể tránh được bằng cách nào đó bằng cách thực thi trật tự sau tất cả các thời gian?

Giải pháp nhỏ nhất là xác định cả hai đối tượng trong cùng một đơn vị dịch, theo đúng thứ tự. Một giải pháp tổng quát hơn là khởi tạo các đối tượng tĩnh của bạn bằng cách sử dụng Construct On First Use Idiom.

Đang sử dụng một con trỏ tĩnh trong ABC để an toàn StaticClass::ii (Tôi không muốn, mặc dù)?

Chừng nào giá trị dereferenced của con trỏ không được sử dụng trong quá trình khởi tạo một đối tượng tĩnh trong một đơn vị dịch thuật nơi các đối tượng nhọn được định nghĩa, vâng, thay thế ABC::def_ii với một con trỏ sẽ được an toàn.


StaticClass::ii sẽ được zero-khởi tạo trong giai đoạn khởi tạo tĩnh ††. Sự thất thoát thứ tự khởi tạo tĩnh liên quan đến giai đoạn khởi tạo động ††.

†† C++ dự thảo tiêu chuẩn [basic.start.static]

Nếu khởi tạo liên tục không được thực hiện, một biến với thời gian lưu trữ tĩnh ([basic.stc.static]) hoặc thời gian lưu trữ ren ([basic.stc. thread]) là zero-initialized ([dcl.init]). Cùng nhau, khởi tạo zero và khởi tạo không đổi được gọi là khởi tạo tĩnh; tất cả khởi tạo khác là khởi tạo động. Việc khởi tạo tĩnh sẽ được thực hiện trước khi bất kỳ khởi tạo động nào diễn ra. [Lưu ý: Khởi tạo động các biến không phải cục bộ được mô tả trong [basic.start.dynamic]; các biến tĩnh cục bộ được mô tả trong [stmt.dcl]. - end note]

+0

Bạn có thể muốn xây dựng trên các giai đoạn khởi tạo tĩnh và động và cách thiết lập thứ tự khởi tạo động bắt buộc. Nếu không câu trả lời của bạn ít hơn một nửa câu chuyện. –

+0

@MaximEgorushkin Tôi đã thêm công cụ về khởi tạo tĩnh và động. Mặc dù nó giúp hiểu tại sao giá trị của 'StaticClass :: ii' chưa được khởi tạo phải bằng 0, tôi không đồng ý về ý nghĩa của nó đối với câu hỏi. – user2079303

+0

Thứ tự a) không khởi tạo, b) khởi tạo tĩnh, c) khởi tạo động? Và vì 'const int StaticClass :: i = 2' là b),' const int ABC :: def_i = StaticClass :: i' là gì? Năng động, tôi sẽ đoán. – toting

Các vấn đề liên quan