Bagikan melalui


Inisialisasi CRT

Artikel ini menjelaskan bagaimana CRT menginisialisasi status global dalam kode asli.

Secara default, linker menyertakan pustaka CRT, yang menyediakan kode startup-nya sendiri. Kode startup ini menginisialisasi pustaka CRT, memanggil inisialisasi global, lalu memanggil fungsi yang disediakan main pengguna untuk aplikasi konsol.

Dimungkinkan, meskipun tidak disarankan, untuk memanfaatkan perilaku tautan khusus Microsoft untuk menyisipkan inisialisasi global Anda sendiri dalam urutan tertentu. Kode ini tidak portabel, dan dilengkapi dengan beberapa peringatan penting.

Menginisialisasi objek global

Pertimbangkan kode C++ berikut (C tidak akan mengizinkan kode ini karena tidak mengizinkan panggilan fungsi dalam ekspresi konstan).

int func(void)
{
    return 3;
}

int gi = func();

int main()
{
    return gi;
}

Menurut standar C/C++, func() harus dipanggil sebelum main() dijalankan. Tapi siapa yang menyebutnya?

Salah satu cara untuk menentukan pemanggil adalah dengan mengatur titik henti di func(), men-debug aplikasi, dan memeriksa tumpukan. Dimungkinkan karena kode sumber CRT disertakan dengan Visual Studio.

Saat menelusuri fungsi pada tumpukan, Anda akan melihat bahwa CRT memanggil daftar penunjuk fungsi. Fungsi-fungsi ini mirip func()dengan , atau konstruktor untuk instans kelas.

CRT mendapatkan daftar penunjuk fungsi dari pengkompilasi Microsoft C++. Ketika pengkompilasi melihat penginisialisasi global, penginisialisasi ini menghasilkan penginisialisasi dinamis di .CRT$XCU bagian di mana CRT adalah nama bagian dan XCU merupakan nama grup. Untuk mendapatkan daftar penginisialisasi dinamis, jalankan perintah dumpbin /all main.obj, lalu cari bagian .CRT$XCU . Perintah hanya berlaku ketika main.cpp dikompilasi sebagai file C++, bukan file C. Ini harus mirip dengan contoh ini:

SECTION HEADER #6
.CRT$XCU name
       0 physical address
       0 virtual address
       4 size of raw data
     1F2 file pointer to raw data (000001F2 to 000001F5)
     1F6 file pointer to relocation table
       0 file pointer to line numbers
       1 number of relocations
       0 number of line numbers
40300040 flags
         Initialized Data
         4 byte align
         Read Only

RAW DATA #6
  00000000: 00 00 00 00                                      ....

RELOCATIONS #6
                                               Symbol    Symbol
Offset    Type              Applied To         Index     Name
--------  ----------------  -----------------  --------  -------
00000000  DIR32             00000000           C         ??__Egi@@YAXXZ (void __cdecl `dynamic initializer for 'gi''(void))

CRT mendefinisikan dua pointer:

  • __xc_a di .CRT$XCA
  • __xc_z di .CRT$XCZ

Tidak ada grup yang memiliki simbol lain yang ditentukan kecuali __xc_a dan __xc_z.

Sekarang, ketika linker membaca berbagai .CRT subbagian (bagian setelah $), itu menggabungkannya dalam satu bagian dan mengurutkannya menurut abjad. Ini berarti bahwa penginisialisasi global yang ditentukan pengguna (yang dimasukkan .CRT$XCUpengkompilasi Microsoft C++ ) selalu datang setelah .CRT$XCA dan sebelum .CRT$XCZ.

Bagian harus menyerupai contoh ini:

.CRT$XCA
            __xc_a
.CRT$XCU
            Pointer to Global Initializer 1
            Pointer to Global Initializer 2
.CRT$XCZ
            __xc_z

Pustaka CRT menggunakan dan __xc_a __xc_z untuk menentukan awal dan akhir daftar inisialisasi global karena cara mereka diletakkan dalam memori setelah gambar dimuat.

Fitur linker untuk inisialisasi

Standar C++ tidak menyediakan cara yang sesuai untuk menentukan urutan relatif di seluruh unit terjemahan untuk inisialisasi global yang disediakan pengguna. Namun, karena linker Microsoft memesan .CRT subbagian menurut abjad, dimungkinkan untuk memanfaatkan urutan ini untuk menentukan urutan inisialisasi. Kami tidak merekomendasikan teknik khusus Microsoft ini, dan mungkin rusak dalam rilis mendatang. Kami telah mendokumenkannya hanya untuk mencegah Anda membuat kode yang rusak dengan cara yang sulit didiagnosis.

Untuk membantu mencegah masalah dalam kode Anda, dimulai di Visual Studio 2019 versi 16.11, kami telah menambahkan dua hal baru secara default : C5247 dan C5248. Aktifkan peringatan ini untuk mendeteksi masalah saat membuat inisialisasi Anda sendiri.

Anda dapat menambahkan penginisialisasi ke nama bagian yang dipesan yang tidak digunakan untuk membuatnya dalam urutan relatif tertentu ke penginisialisasi dinamis yang dihasilkan kompilator:

#pragma section(".CRT$XCT", read)
// 'i1' is guaranteed to be called before any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCT")) type i1 = f;

#pragma section(".CRT$XCV", read)
// 'i2' is guaranteed to be called after any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCV")) type i2 = f;

Nama .CRT$XCT dan .CRT$XCV tidak digunakan oleh pengkompilasi atau pustaka CRT saat ini, tetapi tidak ada jaminan bahwa mereka akan tetap tidak digunakan di masa depan. Dan, variabel Anda masih dapat dioptimalkan jauh oleh pengkompilasi. Pertimbangkan potensi masalah rekayasa, pemeliharaan, dan portabilitas sebelum mengadopsi teknik ini.

Lihat juga

_initterm, _initterm_e
File runtime C (CRT) dan C++ Standard Library (STL) .lib