5. Dizileri kullanırken sık karşılaşılan tuzaklar.
5.1 Tuzak: Güvenli olmayan tipte bağlantıya güvenmek.
Tamam, size küresellerin (çeviri birimi dışında erişilebilen ad alanı kapsam değişkenleri) Evil ™ olduğu söylendi veya kendiniz öğrendiniz. Ama ne kadar gerçekten Evil ™ olduklarını biliyor muydunuz? [Main.cpp] ve [numbers.cpp] dosyalarından oluşan aşağıdaki programı göz önünde bulundurun:
// [main.cpp]
#include <iostream>
extern int* numbers;
int main()
{
using namespace std;
for( int i = 0; i < 42; ++i )
{
cout << (i > 0? ", " : "") << numbers[i];
}
cout << endl;
}
// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Windows 7'de bu, hem MinGW g ++ 4.4.1 hem de Visual C ++ 10.0 ile iyi derler ve bağlantı kurar.
Türler eşleşmediğinden, programı çalıştırdığınızda çöküyor.
Resmi açıklama: Programın Tanımsız Davranışı (UB) vardır ve çökmek yerine, sadece asılabilir veya belki de hiçbir şey yapamaz veya ABD, Rusya, Hindistan başkanlarına tehdit eden e-postalar gönderebilir, Çin ve İsviçre ve Burun Daemonlarının burnundan uçmasını sağla.
Uygulama içi açıklama: main.cpp
Dizide, diziyle aynı adrese yerleştirilmiş bir işaretçi olarak değerlendirilir. 32 bit yürütülebilir dosya için bu int
, dizideki ilk değerin bir işaretçi olarak değerlendirildiği anlamına gelir
. Yani, içinde değişken içerdiği veya görünür, içerdiği . Bu, programın geleneksel olarak ayrılmış ve tuzağa neden olan adres alanının en altındaki belleğe erişmesine neden olur. Sonuç: bir çarpışma yaşarsınız.main.cpp
numbers
(int*)1
Derleyiciler bu hatayı teşhis etmeme haklarına tamamen sahiptir, çünkü C ++ 11 §3.5 / 10 bildirimler için uyumlu türlerin gerekliliği hakkında,
[N3290 §3.5 / 10]
Tür kimliğine ilişkin bu kuralın ihlali için bir tanılama gerekmez.
Aynı paragraf izin verilen varyasyonu detaylandırır:
… Bir dizi nesnesi için bildirimler, bağlı bir ana dizinin varlığına veya yokluğuna göre farklılık gösteren dizi türlerini belirtebilir (8.3.4).
İzin verilen bu varyasyon, bir çeviri biriminde dizi olarak ve başka bir çeviri biriminde işaretçi olarak ad bildirilmesini içermez.
5.2 Tuzak: Erken optimizasyon ( memset
& arkadaşlar) yapmak.
Henüz yazılmadı
5.3 Tuzak: Eleman sayısını elde etmek için C deyimini kullanma.
Derin C deneyimiyle yazmak doğaldır…
#define N_ITEMS( array ) (sizeof( array )/sizeof( array[0] ))
Bir yana array
gereken ilk elemana işaretçi Bozunma sentezleme sizeof(a)/sizeof(a[0])
aynı zamanda şu şekilde yazılabilir
sizeof(a)/sizeof(*a)
. Aynı anlama gelir ve nasıl yazılırsa yazılsın , dizinin sayı öğelerini bulmak için C deyimidir .
Ana tuzak: C deyim tipik değildir. Örneğin, kod…
#include <stdio.h>
#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))
void display( int const a[7] )
{
int const n = N_ITEMS( a ); // Oops.
printf( "%d elements.\n", n );
}
int main()
{
int const moohaha[] = {1, 2, 3, 4, 5, 6, 7};
printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
display( moohaha );
}
bir işaretçi geçirir N_ITEMS
ve bu nedenle büyük olasılıkla yanlış sonuç verir. Windows 7 32-bit yürütülebilir olarak derlenen üretir…
7 eleman, çağrı ekranı ...
1 eleman.
- Derleyici
int const a[7]
sadece yeniden yazar int const a[]
.
- Derleyici yeniden yazar
int const a[]
için int const* a
.
N_ITEMS
bu nedenle bir işaretçi ile çağrılır.
- 32 bit yürütülebilir dosya için
sizeof(array)
(işaretçi boyutu) 4 olur.
sizeof(*array)
sizeof(int)
32 bit yürütülebilir dosya için de 4 olan eşdeğerdir .
Çalışma zamanında bu hatayı tespit etmek için şunları yapabilirsiniz…
#include <assert.h>
#include <typeinfo>
#define N_ITEMS( array ) ( \
assert(( \
"N_ITEMS requires an actual array as argument", \
typeid( array ) != typeid( &*array ) \
)), \
sizeof( array )/sizeof( *array ) \
)
7 öğe, display çağrısı ...
Onaylama başarısız oldu: ("N_ITEMS bağımsız değişken olarak gerçek bir dizi gerektiriyor", typeid (a)! = Typeid (& * a)), runtime_detect ion.cpp, line 16 dosyası
Bu uygulama Runtime'dan alışılmadık bir şekilde sonlandırmasını istedi.
Daha fazla bilgi için lütfen uygulamanın destek ekibiyle iletişime geçin.
Çalışma zamanı hata tespiti, hiçbir algılamadan daha iyidir, ancak biraz işlemci süresi ve belki de daha fazla programcı zamanı harcar. Derleme zamanında algılama ile daha iyi! Ve C ++ 98 ile yerel tür dizilerini desteklemekten mutluluk duyarsanız, bunu yapabilirsiniz:
#include <stddef.h>
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
#define N_ITEMS( array ) n_items( array )
G ++ ile ilk tam program yerine bu tanımı derleme, ben var…
M: \ count> g ++ compile_time_detection.cpp
compile_time_detection.cpp: 'void display (const int *)'
işlevinde: compile_time_detection.cpp: 14: hata: 'n_items (const int * &)' çağrısı için eşleşen işlev yok
M: \ sayım> _
Nasıl çalışır: dizi referans olarak geçirilir n_items
ve bu nedenle ilk öğeye işaretçi çürümez ve işlev yalnızca tür tarafından belirtilen öğelerin sayısını döndürebilir.
C ++ 11 ile bunu yerel tür diziler için de kullanabilirsiniz ve bir dizinin öğe sayısını bulmak için güvenli C ++ deyim türüdür
.
5.4 C ++ 11 ve C ++ 14 tuzağı: Bir constexpr
dizi boyutu işlevi kullanma .
C ++ 11 ve sonraki sürümlerde bu doğaldır, ancak gördüğünüz gibi C ++ 03 işlevini değiştirmek için tehlikeli!
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
ile
using Size = ptrdiff_t;
template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }
burada önemli değişiklik, constexpr
bu fonksiyonun bir derleme zaman sabiti üretmesine izin veren kullanımıdır .
Örneğin, C ++ 03 işlevinin aksine, böyle bir derleme zaman sabiti, başka bir boyutla aynı boyuttaki bir diziyi bildirmek için kullanılabilir:
// Example 1
void foo()
{
int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
constexpr Size n = n_items( x );
int y[n] = {};
// Using y here.
}
Ancak constexpr
sürümü kullanarak bu kodu düşünün :
// Example 2
template< class Collection >
void foo( Collection const& c )
{
constexpr int n = n_items( c ); // Not in C++14!
// Use c here
}
auto main() -> int
{
int x[42];
foo( x );
}
Tuzak: Temmuz 2015 itibariyle, yukarıdaki MinGW-64 5.1.0 ile derlenir
-pedantic-errors
ve gcc.godbolt.org/ adresindeki çevrimiçi derleyicilerle test edilir , ayrıca clang 3.0 ve clang 3.2 ile yapılır, ancak clang 3.3, 3.4 ile değil. 1, 3.5.0, 3.5.1, 3.6 (rc1) veya 3.7 (deneysel). Ve Windows platformu için önemli, Visual C ++ 2015 ile derlenmez. Bunun nedeni, constexpr
ifadelerde referansların kullanımı ile ilgili bir C ++ 11 / C ++ 14 ifadesidir:
C ++ 11 C ++ 14 $ 5.19 / 2 dokuz
inci çizgi
Bir koşullu ekspresyonu e
a, çekirdek sabit ifade değerlendirilmesi sürece e
soyut makine (1.9), aşağıdaki işlemlerden birini değerlendirmek olur kuralları gereğince,:
⋮
- bir kimlik-sentezleme referans önceki bir başlatma vardır ve her iki sürece referans tipte bir değişken ya da veri elemanına değinmektedir
- sabit bir ifade ile başlatılır veya
- ömrü e'nin değerlendirilmesinde başlayan bir nesnenin statik olmayan bir veri üyesidir;
Kişi her zaman daha ayrıntılı yazabilir
// Example 3 -- limited
using Size = ptrdiff_t;
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = std::extent< decltype( c ) >::value;
// Use c here
}
… Ancak bu Collection
ham bir dizi olmadığında başarısız olur .
Diziler olmayan koleksiyonlarla başa çıkmak için bir n_items
fonksiyonun aşırı yüklenebilirliğine ihtiyaç duyulur
, fakat aynı zamanda derleme zamanı için dizi büyüklüğünün derleme zamanı gösterimi gerekir. C ++ 11 ve C ++ 14'te de iyi çalışan klasik C ++ 03 çözümü, işlevin sonucunu bir değer olarak değil, işlev sonuç türü aracılığıyla rapor etmesine izin vermektir . Örneğin şöyle:
// Example 4 - OK (not ideal, but portable and safe)
#include <array>
#include <stddef.h>
using Size = ptrdiff_t;
template< Size n >
struct Size_carrier
{
char sizer[n];
};
template< class Type, Size n >
auto static_n_items( Type (&)[n] )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
template< class Type, size_t n > // size_t for g++
auto static_n_items( std::array<Type, n> const& )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
#define STATIC_N_ITEMS( c ) \
static_cast<Size>( sizeof( static_n_items( c ).sizer ) )
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = STATIC_N_ITEMS( c );
// Use c here
(void) c;
}
auto main() -> int
{
int x[42];
std::array<int, 43> y;
foo( x );
foo( y );
}
Dönüş türü seçimi hakkında static_n_items
: bu kod kullanılmaz, std::integral_constant
çünkü std::integral_constant
sonuç doğrudan constexpr
orijinal değeri yeniden ortaya koyar ve bir değer olarak temsil edilir . Bir Size_carrier
sınıf yerine , işlevin doğrudan bir diziye başvuru döndürmesine izin verilebilir. Ancak, herkes bu sözdizimine aşina değildir.
Adlandırma hakkında: constexpr
-invalid-nedeniyle-başvuru sorunu için bu çözümün bir parçası , derleme zamanı seçimini sürekli açık yapmaktır.
İnşallah o-bir-orada-referans-dahil- constexpr
sorunu - sorunu C ++ 17 ile giderilecektir, ama o zamana kadar STATIC_N_ITEMS
yukarıdaki gibi bir makro taşınabilirlik, örneğin clang ve Visual C ++ derleyicileri, tutma türü verir Emniyet.
İlgili: makrolar kapsamlara saygı göstermez, bu nedenle ad çakışmalarını önlemek için bir ad öneki kullanmak iyi bir fikir olabilir, örn MYLIB_STATIC_N_ITEMS
.