5. 배열을 사용할 때의 일반적인 함정.
5.1 함정 : 안전하지 않은 유형 안전 연결.
전역 (번역 단위 외부에서 액세스 할 수있는 네임 스페이스 범위 변수)이 Evil ™이라는 말을 들었습니다. 그러나 그들이 실제로 얼마나 악한 지 아십니까? 두 개의 파일 [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에서는 MinGW g ++ 4.4.1 및 Visual C ++ 10.0과 잘 컴파일되고 링크됩니다.
유형이 일치하지 않으므로 프로그램을 실행할 때 충돌이 발생합니다.
공식적인 설명 :이 프로그램에는 UB (Undefined Behavior)가 있으므로 충돌하지 않고 그냥 중단하거나 아무 것도하지 않거나 미국, 러시아, 인도의 대통령에게 위협 이메일을 보낼 수 있습니다. 중국과 스위스, 코에서 비강 데몬을 날리십시오.
실습 설명 : main.cpp
배열에서 배열과 동일한 주소에 배치 된 포인터로 취급됩니다. 32 비트 실행 파일의 경우 이는 int
배열 의 첫 번째
값이 포인터로 처리됨을 의미합니다. 즉, 상품 변수가 포함되어 있거나 나타납니다이 포함합니다 . 이로 인해 프로그램은 주소 공간의 맨 아래에서 메모리에 액세스하게되는데, 이는 일반적으로 예약되어 있고 트랩을 유발합니다. 결과 : 충돌이 발생합니다.main.cpp
numbers
(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] ))
array
필요한 경우 첫 번째 요소에 대한 포인터로 붕괴 되기 때문에 식 sizeof(a)/sizeof(a[0])
을로 쓸 수도 있습니다
sizeof(a)/sizeof(*a)
. 그것은 똑같은 것을 의미하며, 그것이 어떻게 작성 되든 그것은 배열의 숫자 요소를 찾는 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]
는 just로 다시 씁니다 int const a[]
.
- 컴파일러는 재 작성
int const a[]
에 int const* a
.
N_ITEMS
따라서 포인터로 호출됩니다.
- 32 비트 실행 파일
sizeof(array)
(포인터 크기)의 경우 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 ion.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
compile_time_detection.cpp : 'void display (const int *)'
함수에서 : compile_time_detection.cpp : 14 : error : 'n_items (const int * &)'에 대한 호출과 일치하는 함수가 없습니다.
M : \ count> _
작동 방식 : 배열은 참조 로 전달 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; }
와
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로 컴파일되지 않습니다. 그 이유는 constexpr
식에서 참조 사용에 대한 C ++ 11 / C ++ 14 문입니다 .
C ++ 11 C ++ 14 $ 5.19 / 2 아홉
번째 대시
조건 표현은 e
A는 핵심 상수 표현 의 평가하지 않는 e
추상 기계 (1.9), 다음 식 중 하나를 평가하는 것의 규칙에 따라, :
⋮
- ID 표현 기준이 선행 초기화를 가지며 하나 않는 참조 형식의 변수 또는 데이터 부재를 의미
- 상수 표현식으로 초기화되거나
- 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 ++ 11 및 C ++ 14에서도 잘 작동하는 클래식 C ++ 03 솔루션은 함수가 결과를 값이 아니라 함수 결과 유형을 통해보고하도록하는 것 입니다. 예를 들면 다음과 같습니다.
// 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
클래스 대신에 함수가 배열에 대한 참조를 직접 반환하도록 할 수 있습니다. 그러나, 모두가 그 구문을 잘 알고있다.
명명 정보 :이 솔루션의 일부로 constexpr
-invalid-인해-을 참조 문제 컴파일 시간 상수의 선택이 명시 적으로 만드는 것입니다.
바라건대 oops-the-the-a-a-a-a-a-a-a-a-in-your- constexpr
문제는 C ++ 17로 수정 될 것이지만, 그때까지 STATIC_N_ITEMS
위와 같은 매크로 는 clang 및 Visual C ++ 컴파일러에 대한 이식성을 제공합니다. 안전.
관련 : 매크로는 범위를 고려하지 않으므로 이름 충돌을 피하려면 이름 접두사를 사용하는 것이 좋습니다 (예 :) MYLIB_STATIC_N_ITEMS
.