C ++ 26 Programlama Dili: Ana Dilde Güvenlik Geliştirmeleri

Güvenlik C ++ 26'da önemli bir konudur ve sözleşmeler muhtemelen en büyük güvenlik özelliğidir. Ancak C ++ 26'nın sunabileceği çok daha fazlası var.

Rainer Grimm yıllardır yazılım mimarı, ekip ve eğitim müdürü olarak çalıştı. C ++ programlama dilleri, Python ve Haskell hakkında makaleler yazmayı seviyor, ancak uzman konferanslarla konuşmayı da seviyor. Modern C ++ blogunda, C ++ tutkusuyla yoğun bir şekilde ilgileniyor.

Bugün C ++ 26'da C ++ 'da tipik güvenlik sorunlarını çözen üç küçük ama önemli iyileştirme yapmak istiyorum.

Bir güvenlik sorunu bulmak zor olabilir. Kodun bu bölümü, bu bölümde tekrar tekrar başvurduğum P2748R5 teklifinden geliyor. Minimum ve yürütülebilir bir programa dönüştürdüm.


// bindReferenceToTemporary.cpp

#include <iostream>
#include <string>
#include <string_view>

const std::string_view& getString() {
    static std::string s = "Hallo Welt!";
    return s;
}

int main() {
    std::cout << getString() << 'n';
}   

GCC derleyicisi zaten önemli bir hata mesajı yayıyor:

kodlamak

kodlamak

İşte kodda bir hata olan başka bir örnek:


struct X {
    const std::map<std::string, int> d_map;
    const std::pair<std::string, int>& d_first;

    
X(const std::map<std::string, int>& map)
        : d_map(map), d_first(*d_map.begin()) {}
};

Kodu yazan herkes, anahtar olarak belirtilen çiftin ilk unsurunun sabit olduğu gerçeğini ihmal etti. Bu geçici bir değişken oluşturur. Sonuç olarak, d_first artık geçerli değil.

Bu bizi bir sonraki güvenliğe götürür.

P2795R5 teklifine atıfta bulunuyorum.

Her şeyden önce, otomatik depolama süresi ve geçici nesnelere sahip nesneler için geçerlidir. Onların tuhaflığı, herhangi bir değerde başlatılmalarıdır. Bu, programın belirsiz bir davranışa sahip olduğu anlamına gelir.

Yani bir soru olmaya devam ediyor: Arşivleme otomatik süresi ne anlama geliyor? Aşağıdaki değişkenlerin otomatik depolama süresi vardır:

  • Engelleme alanı olan ve açıkça olmayan değişkenler static,, thread_local VEYA extern ilan edildi. Bu değişkenlerin belleği yalnızca tıkanma tamamlanana kadar geçerli kalır.
  • Parametrelerin bir alanına ait değişkenler, örneğin bir işlev. Parametrelerin alanı çözüldüğünde otomatik olarak yok edilirler.

Teklifin iki örneği şu terimleri göstermelidir:


extern void f(int);

int main() {
  int x;     // default-initialized, value of x is indeterminate
  f(x);      // glvalue-to-prvalue conversion has undefined behaviour
}   

void f(T&);
void g(bool);

void h() {
  T* p;    // uninitialized, has erroneous value (e.g. null)
  bool b;  // uninitialized, erroneous value may not be valid bool

  f(*p);   // dereferencing has undefined behaviour
  g(b);    // undefined behaviour if b is invalid
}

Konuya ulaşmak için: Teklif, C ++ 26'daki yanlış programlarda belirsiz bir davranış olan başlatılmayan okuma süreçlerini C ++ 23'e dönüştürdü.

Tabii ki, otomatik değişkenlerin tam olarak başlatılması nispeten karmaşık bir süreç olabilir. Bu nedenle bir feragat mekanizması vardır:

Öznitelik [[indeterminate]] devre dışı bırakma mekanizmasını temsil eder. Bu işlev sadece uzmanlar için tasarlanmıştır. Öznitelik, program arızalı olmadan başlatılmamış otomatik depolamalı değişkenleri aydınlatmanıza olanak tanır. Aşağıdaki basitleştirilmiş örnek tekliften gelir:


int x [[indeterminate]];
std::cin >> x;

[[indeterminate]] int a, b[10], c[10][10];
compute_values(&a, b, c, 10);

// This class benefits from avoiding determinate-storage initialization guards.
struct SelfStorage {
  std::byte data_[512];
  void f();   // uses data_ as scratch space
};

SelfStorage s [[indeterminate]];   // documentation suggested this

void g([[indeterminate]] SelfStorage s = SelfStorage());   // same; unusual, but plausible

Son güvenlik işlevi eksik türleri etkiler.

Eksik bir tür, yalnızca beyanname için bir tür veri türüdür, ancak tanım yoktur. Eksik bir türe işaretçi veya referans mükemmeldir. Bu tür verilerin boyutunu, düzenini veya üye işlevlerini gerektiren işlemler yanlıştır.

Yeni işlev bir şekilde daha spesifiktir: bu tür bir sınıfın önemsiz bir yok edicisi ve sınıfın spesifik bir dizokasyon fonksiyonu olmadığı sürece, bir işaretçinin eksik bir sınıf türüne ortadan kaldırılması doğrudur. Bu, derleyicinin E sınıfının yoksulluğunu oluşturduğu anlamına gelir. operator delete Sınıfta aşırı yüklenmedi.

Teklif, önemsiz bir muhrip ve önemsiz olmayan bir muhrip arasındaki güzel farkı göstermek için iyi bir örnek sunuyor:

Banal yıkıcılar:


// trivialDestructor.cpp
    
namespace xyz {
  struct Widget; // forward decl
  Widget *new_widget();
} // namespace xyz

int main() {
  xyz::Widget *p = xyz::new_widget();
  delete p;
}

namespace xyz {
struct Widget {
  const char *d_name;
  int         d_data;
  // (implicit) trivial destructor
  // This is the only difference.
}; 

Widget *new_widget() { 
    return new Widget(); 
}
} // namespace xyz

Banal Olmayan Destroyer:


// nontrivialDestructor.cpp

namespace xyz {
  struct Widget; // forward decl
  Widget *new_widget();
} // namespace xyz

int main() {
  xyz::Widget *p = xyz::new_widget();
  delete p;
}
namespace xyz {
struct Widget {
  const char *d_name;
  int         d_data;
  ~Widget() {} // nontrivial dtor
  // This is the only difference.
};

Widget *new_widget() { 
    return new Widget(); 
}
} // namespace xyz

İkinci örnek, önemsiz olmayan bir muhrip içerir. Teklife göre, derleme bir hata mesajı ile durduruldu:

Yoğun olmayan muhrip

Yoğun olmayan muhrip

Bir sonraki makalemde, modelden modelin etrafında bazı küçük iyileştirmelerle ilgileneceğim. Bu gelişmeler esas olarak C ++ 'da tutarsız davranışı ortadan kaldırmaya hizmet eder.


(RME)


Yayımlandı

kategorisi

yazarı:

Etiketler:

Yorumlar

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir