C++ Dilinde constinit
- Yusuf Hançar
- Feb 21
- 4 min read
C++20'de tanıtılan önemli bir özellik `constinit`'dir; bu özellik, statik depolama süresine sahip değişkenlerin derleme zamanında başlatılmasını sağlamaya yardımcı olur.
` constinit`, statik veya thread depolama süresine sahip bir değişkenin derleme zamanında başlatılmasını gerektiren bir anahtar kelimedir.
`constexpr`'dan farklı olarak, derleme zamanındaki sabit ifadeleri belirtmekle birlikte, `constinit` özellikle statik depolama süresine sahip değişkenler için compile-time başlatmasını garanti etmeye odaklanır. Bu özellik, uninitialized bellek erişimi ile ilgili potansiyel sorunları önlemesi açısından kritik öneme sahiptir.
Derleme Zamanı Başlatma: İki Aşama
C++ dilinde, statik depolama süresine sahip değişkenlerin başlatılması 2 aşamada gerçekleşir:
Sabit Başlatma: Bu aşama derleme zamanında gerçekleşir. Değişken, program çalışmaya başlamadan önce tanımlanmış bir duruma getirilir.
Örneğin, `constinit int count = 5;` kullanıyorsanız, değer derleme sırasında ayarlanır.
Çalışma Zamanı Başlatma: Eğer bir değişken sabit başlatma aşamasında başlatılmazsa, zero initialize yapılır; bu da onun sıfır olan öngörülebilir bir değeri tutacağı anlamına gelir.
Eğer bir değişken sabit bir ifade ile başlatılmışsa, sabit başlatma gerçekleşir ve herhangi bir run-time yükü ortadan kalkar.
constinit, constexpr ve const Arasındaki İlişki
`constinit`'i tam olarak anlamak için, onu `constexpr` ve `const`'tan ayırmak önemlidir. Her bir anahtar kelimenin kendi anlamları vardır:
constexpr: Bu anahtar kelime, derleme zamanında değerlendirilebilen değerleri temsil eder. Statik depolama süresine bağlı olmayan ifadeler için esneklik sağlar.
const: Bu anahtar kelime, değişmezliği belirtir ancak derleme zamanı başlatmayı zorunlu kılmaz.
constinit: Bu anahtar kelime, statik depolama süresine sahip değişkenlerin derleme zamanında başlatılmasını özellikle garanti eder ve `constexpr` ile `const` arasında bir köprü kurar.
Bu ayrımları anlamak, geliştiricilerin performans ve güvenlik hedefleriyle uyumlu bilinçli tasarım seçimleri yapmalarına olanak tanır.
Kod örnekleriyle pekiştirelim...
int val = expr;
*****************************
AÇIKLAMA : burada initialization hem run-time hem de compile-time da initialization yapılabilir.
*****************************
constinit int val = expr;
*****************************
AÇIKLAMA : bu değişken constant initialize edilmek zorundadır.
dynamic initialization'a tabi tutulmaz.
constant initialize edilmezse syntax error olur.
*****************************
int foo(int x)
{
return x * x;
}
int main ()
{
constexpr int val = foo(5);
}
*****************************
AÇIKLAMA : foo() constant expression değildir.
*****************************
CEVAP : syntax error
*****************************
constexpr int foo(int x)
{
return x * x;
}
int main ()
{
constexpr int val = foo(5);
}
*****************************
AÇIKLAMA : foo() constant expression
*****************************
CEVAP : legal
*****************************
constexpr kendisi bir tür olmamasına rağmen const anahtar sözcüğünü de beraberinde getirir.
constexpr int foo(int x)
{
return x * x;
}
constexpr const int val = foo(5);
int main()
{
val++;
}
*****************************
AÇIKLAMA : increment of read-only variable ‘val’
*****************************
CEVAP : syntax error
*****************************
istenilen ise hem sabit ifadesi ile ilk değer vermek zorunlu olsun (böylece static order initialization fiaskodan etkilenmesin) hem de değişken const olmasın (mutable olsun)
constexpr int foo(int x)
{
return x * x;
}
constinit int val = foo(5);
int main()
{
val++;
}
*****************************
AÇIKLAMA : const yok
*****************************
CEVAP : legal
*****************************
"constinit" özellikle thread_local değişkenler için maliyeti ciddi ölçüde düşürebilir. "thread_local" bir değişken tanımlandığında arka planda derleyici bazı koruma mekanizması işletebilir ancak bu zaten compile-time da değerini alacağı için bu kodlar üretilmeyecektir.
thread_local Değişkenlerin Maliyeti
Normalde thread_local olarak tanımlanan değişkenler, her thread için ayrı bir kopyaya sahip olur. Ancak, bu değişkenlerin başlatılması için derleyicinin bazı koruma mekanizmaları (guard code) eklemesi gerekebilir. Bu, özellikle dinamik başlatma gerektiren değişkenlerde maliyetlidir.
thread_local int val = 11;
void func()
{
std::cout << val << "\n";
}
int main()
{
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
}
*****************************
AÇIKLAMA : val, her thread'in kendi kopyasına sahip olacak şekilde başlatılır. Ancak burada derleyici, değişkenin yalnızca bir kez başlatıldığından emin olmak için ekstra kod (örneğin mutex gibi mekanizmalar) üretebilir.
*****************************
constinit kullanarak...
constinit thread_local int val = 11;
void func()
{
std::cout << val << "\n";
}
int main()
{
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
}
*****************************
AÇIKLAMA :
Derleyici ekstra başlatma kodu üretmez.
Thread başına ek bir çalışma zamanı maliyeti (runtime overhead) oluşmaz.
Performans açısından daha verimli olur.
*****************************
global değişkenler, static yerel değişkenler, sınıfların static veri elemanları constinit tanımlanabilir.
constinit auto val = 10;
int foo()
{
static constinit int val = 0;
return ++val;
}
class Data {
public :
static inline constinit int val = 10;
};
constexpr std::array<int, 4> get_array()
{
return { 10, 20, 30, 40 };
}
constinit auto g_arr = get_array();
int main()
{
for (auto i : g_arr)
{
std::cout << i << ' ';
}
// g_arr is not const
g_arr[1]++;
g_arr[2] += 100;
std::cout << "\n";
for (auto i : g_arr)
{
std::cout << i << ' ';
}
}
*****************************
AÇIKLAMA : constinit, bir değişkenin derleme zamanında başlatılmasını garanti eder ancak onu const yapmaz.
Yani, g_arr derleme zamanında başlatılacak olsa bile değiştirilebilir (mutable) kalır.
Bu yüzden g_arr[1]++; gibi işlemler geçerlidir ve çalışacaktır.
*****************************
CEVAP :
10 20 30 40
10 21 130 40
*****************************
#include <iostream>
#include <array>
#include <algorithm>
template <std::size_t N>
constexpr std::array<int, N> get_array()
{
return std::array<int, N>{0};
}
constinit auto g_arr = get_array<10>();
int main()
{
for (auto idx : g_arr)
{
std::cout << idx << ' ';
}
// İlk 2 parametresi range
std::for_each(std::begin(g_arr), std::end(g_arr), [](int& r){ ++r; });
std::cout << "\n";
for (auto idx : g_arr)
{
std::cout << idx << ' ';
}
}
*****************************
CEVAP :
0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1
*****************************
Komentáře