5. 使用数组时常见的陷阱。
5.1陷阱:信任类型不安全链接。
好的,你已经被告知,或者你自己已经发现,globals (namespace
可以在翻译单元外访问的范围变量)
邪恶™。但是你知道他们有多邪恶吗?考虑到
下面的程序,由两个文件[main.cpp]和[numbers.cpp]组成:
// [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中,这个编译和链接与mingwg++ 4.4.1和
Visual c++ 10.0。
由于类型不匹配,程序在运行时崩溃。
形式解释:程序具有未定义行为(UB),而不是
因此,它可以挂起来,或者什么都不做,或者它
可以向美国、俄罗斯、印度的总统发送威胁邮件,
中国和瑞士,让鼻子里的恶魔飞出来。
实际解释:在main.cpp中,数组被视为一个指针,被放置
和数组的地址一样。对于32位可执行文件,这意味着第一个
数组中的Int值,被视为指针。例如,在main.cpp中
数字变量包含(int*)1。这导致了
访问内存的程序在地址空间的最下面,也就是
传统的保留和陷阱造成。结果:你会撞车。
编译器完全有权利不诊断这个错误,
因为c++ 11§3.5/10规定了兼容类型的要求
对于声明,
[N3290§3.5/10)
违反此类型标识规则不需要进行诊断。
同一段详细说明了允许的变化:
数组对象的声明可以指定数组类型
区别在于是否存在主数组边界(8.3.4)。
这种允许的变化不包括将名称声明为其中的数组
翻译单元,以及作为另一个翻译单元的指针。
5.2陷阱:过早优化(memset和朋友)。
还没写
5.3陷阱:使用C语言来获取元素的个数。
有丰富的C语言经验,很自然地会写……
#define N_ITEMS( array ) (sizeof( array )/sizeof( array[0] ))
由于数组在需要时衰减为指向第一个元素的指针,因此
表达式sizeof(a)/sizeof(a[0])也可以写成
sizeof (a) / sizeof(*)。意思都一样,不管怎么说
它是用C语言编写的,用于查找数组的数字元素。
主要缺陷:C习惯用法不是类型安全的。例如,代码
...
#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 );
}
将一个指针传递给N_ITEMS,因此很可能产生一个错误
结果。在Windows 7中编译为32位可执行文件,生成…
7个元素,调用显示…
1的元素。
编译器将int const a[7]重写为int const a[]。
编译器将int const a[]重写为int const* a。
因此,用指针调用N_ITEMS。
对于32位可执行文件,sizeof(数组)(指针的大小)则为4。
Sizeof (*array)等价于Sizeof (int),对于32位可执行文件也是4。
为了在运行时检测这个错误,您可以执行…
#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个元素,调用显示…
断言失败:("N_ITEMS需要一个实际的数组作为参数",typeid(a) != typeid(&*a)),文件runtime_detect . (&*a))
离子。cpp,第16行
此应用程序请求运行时以一种不寻常的方式终止它。
请联系应用程序的支持团队以获得更多信息。
运行时错误检测比不检测好,但它会浪费一些时间
处理器时间,可能还有更多的程序员时间。更好的检测
编译时间!如果你愿意在c++ 98中不支持局部类型数组,
然后你可以这样做:
#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++编译第一个完整的程序,
我有…
M:\count> g++ compile_time_detection.cpp
编译时间检测.cpp:在函数void display(const int*):
编译时间检测.cpp:14:错误:没有匹配函数调用'n_items(const int*&)'
M: \数> _
它是如何工作的:数组通过引用传递到n_items,所以它做
而不是衰减到指向第一个元素的指针,函数可以返回
类型指定的元素个数。
在c++ 11中,你也可以将它用于局部类型的数组,它是类型安全的
c++习惯用法,用于查找数组的元素数量。
5.4 c++ 11和c++ 14的缺陷:使用constexpr数组大小函数。
在c++ 11和以后的版本中,这是很自然的,但正如你将看到的那样危险!,
替换c++ 03函数
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
with
using Size = ptrdiff_t;
template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }
其中重要的变化是使用constexpr,这允许
此函数生成一个编译时间常数。
例如,相对于c++ 03函数,这样的编译时间常数
可以用来声明一个与另一个相同大小的数组:
// 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.
}
但是考虑使用constexpr版本的代码:
// 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 );
}
缺陷:截至2015年7月,上述编译与MinGW-64 5.1.0
-pedantic-errors,
在gcc.godbolt.org/上使用在线编译器进行测试,也使用clang 3.0
和clang 3.2,但不与clang 3.3, 3.4.1, 3.5.0, 3.5.1, 3.6 (rc1或
3.7(实验)。对于Windows平台来说,重要的是它不能编译
Visual c++ 2015。原因是c++ 11/ c++ 14关于使用的语句
constexpr表达式中的引用:
C++11 C++14 $5.19/2 nine
th
dash
A conditional-expression e is a core constant expression unless the evaluation
of e, following the rules of the abstract machine (1.9), would evaluate one of the
following expressions:
⋮
an id-expression that refers to a variable or data member of reference type
unless the reference has a preceding initialization and either
it is initialized with a constant expression or
it is a non-static data member of an object whose lifetime began within
the evaluation of e;
一个人总是可以写得更详细
// 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
}
但是当Collection不是一个原始数组时,这就失败了。
类的可重载性来处理非数组的集合
N_items函数,但是为了在编译时使用,需要一个编译时
数组大小的表示。以及经典的c++ 03解决方案,它工作得很好
同样在c++ 11和c++ 14中,是让函数不以值的形式报告其结果
而是通过它的函数结果类型。比如这样:
// 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 );
}
关于static_n_items返回类型的选择:这段代码没有使用std::integral_constant
因为用std::integral_constant表示结果
直接作为constexpr值,重新引入原来的问题。而不是
对于Size_carrier类,1可以让函数直接返回
引用数组。然而,并不是每个人都熟悉这种语法。
关于命名:这个解决方案的一部分是constexpr-invalid-due-to引用
问题是要明确地选择编译时间常数。
希望“哎哟,在你的constexpr中有一个引用”的问题将被修复
但在此之前,像上面STATIC_N_ITEMS这样的宏会产生可移植性,
例如,clang和Visual c++编译器,保留类型安全。
相关:宏不尊重作用域,因此为了避免名称冲突,可以使用
使用名称前缀是个好主意,例如MYLIB_STATIC_N_ITEMS。