2017-02-20 20 views
19

Clang 3.9'a yönelik sarkan atıfları algılar, geçici olarak kullanılan bellekleri yeniden kullanır. (my_optional bunu göstermek için sadece basit bir örnektir) Yukarıda gibi kod şey tonlarcaGeçici

template <class T> 
class my_optional 
{ 
public: 
    bool has{ false }; 
    T value; 

    const T& get_or_default(const T& def) 
    { 
     return has ? value : def; 
    } 
}; 

void use(const std::string& s) 
{ 
    // ... 
} 

int main() 
{ 
    my_optional<std::string> m; 
    // ... 
    const std::string& s = m.get_or_default("default value"); 
    use(s); // s is dangling if default returned 
} 

:

Bu kod UB (basitleştirilmiş kod) 'dir.

UB'den dolayı tüm clang derleyici 3.9'dan beri bu belleği yeniden kullanmaya başlar ve bu yasal bir davranıştır.

Sorun şu ki: ,'u derleme zamanında ya da çalışma zamanında sanitizer gibi bir şeyle ilgili sarkan referansları nasıl algılar? Clang dezenfektanı onları tespit edemez.

Upd. Lütfen cevaplamayın: "std::optional kullanın". Dikkatle okuyun: soru bu konuda DEĞİLDİR.
Upd2. Lütfen cevaplamayın: "kod tasarımınız kötü". Dikkatle okuyun: soru kod tasarımı ile ilgili DEĞİLDİR. get_or_default verilen argüman gerçek rvalue ise bunun yerine seçilecek,

const T& get_or_default(T&& rvalue) = delete; 

, bu nedenle derleme başarısız olur:

+0

@Someprogrammerdude Teşekkür ederim, biliyorum. Asıl nokta şudur: sabit değil, fakat std :: string ("varsayılan değer") örtülü olarak yapılmış, bu geçici bir durumdur ve o satırdan sonra ölür. Yani sıradaki satır sarkıyor. – vladon

+0

Numara. Dize * geçici * nesne _dies_ satırın sonunda. Referans, 'use (s)' satırında sarkıyor. – vladon

+1

@Someprogrammerdude "bir işlev çağrısındaki bir referans parametresine geçici bir sınırlama, bu işlev çağrısını içeren tam ifadenin sonuna kadar var: işlev, tam ifadeyi aşan bir başvuru döndürürse," http://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary – vladon

cevap

22

ek bir aşırı ekleyerek bu özel API, suistimallere algılayabilir.

Çalışma zamanında bu tür hataları algılamakta olduğu gibi, Clang'ın AddressSanitizer'ı kullanarak-return-return (ASAN_OPTIONS=detect_stack_use_after_return=1) ve/veya use-after-scope (-fsanitize-address-use-after-scope) algılama etkinleştirilmiş kullanmayı deneyin.

+0

evet! Teşekkürler! Bu seçeneklerle ASan onları algılar :-) – vladon

3

Bu ilginç bir soru. Sarkan ref'in asıl nedeni, bir değer referansını, bir değer olarak kabul etmiş gibi kullanmanızdır. (bir rvalue gerçekten olan) geçici için bunu bir ref geçirirseniz,

class my_optional 
{ 
public: 
    bool has{ false }; 
    T value; 

    const T& get_or_default(const T&& def) 
    { 
     throw std::invalid_argument("Received a rvalue"); 
    } 

    const T& get_or_default(const T& def) 
    { 
     return has ? value : def; 
    } 
}; 

O yolu: o kodun çok fazla değil varsa

, bir özel durum için bu şekilde deneyebilirsiniz Bir istisna alacaksınız, yakalayabileceksiniz ya da en azından kısa sürede iptal edebileceksiniz.

class my_optional 
{ 
public: 
    bool has{ false }; 
    T value; 

    const T get_or_default(const T&& def) 
    { 
     return get_or_default(static_cast<const T&>(def)); 
    } 

    const T& get_or_default(const T& def) 
    { 
     return has ? value : def; 
    } 
}; 

başka olasılık sormak Clang derleyici kesmek olacaktır: Bir rvalue geçirilmesi halinde

Alternatif olarak, geçici bir değer (ve bir ref) dönmek için zorlayarak basit düzeltme deneyebilirsiniz o

3

Sen Explicit kütüphanesinden lvalue_ref sarmalayıcı deneyebilirsiniz ... Ben bu teknikler için kullanılan yeterli değilim tarafından, yöntem lvalue veya rvalue geçirilen olup olmadığını tespit etmek için. Tek bir bildirimde geçici olarak istenmeyen bir bağlanma olmasını engeller, örneğin:

const T& get_or_default(lvalue_ref<const T> def) 
{ 
    return has ? value : def.get(); 
}