포인터 붕괴에 대한 배열은 무엇입니까? 배열 포인터와 관련이 있습니까?
std::decay
C ++ 14에서 단항 +에 비해 배열을 부패시키는 덜 모호한 방법입니다.
+a
와 +b
C에서 합법적 ++는 C (에 불법 C11 6.5.3.3/1 "단항의 피연산자 +
또는 -
운영자는 있어야한다 산술 유형 ")
포인터 붕괴에 대한 배열은 무엇입니까? 배열 포인터와 관련이 있습니까?
std::decay
C ++ 14에서 단항 +에 비해 배열을 부패시키는 덜 모호한 방법입니다.
+a
와 +b
C에서 합법적 ++는 C (에 불법 C11 6.5.3.3/1 "단항의 피연산자 +
또는 -
운영자는 있어야한다 산술 유형 ")
답변:
배열은 포인터로 "부패"한다고합니다. 로 선언 된 C ++ 배열은 int numbers [5]
다시 지정할 수 없습니다 numbers = 0x5a5aff23
. 즉 말할 수 없습니다 . 더 중요한 것은 붕괴라는 용어는 유형과 치수의 손실을 의미합니다. 차원 정보 (카운트 5)를 잃어 numbers
붕괴되고 int*
유형은 int [5]
더 이상 없습니다 . 부패가 발생하지 않는 경우는 여기를 참조하십시오 .
값으로 배열을 전달하는 경우 실제로 수행하는 것은 포인터를 복사하는 것입니다. 배열의 첫 번째 요소에 대한 포인터가 매개 변수에 복사됩니다 (이 유형은 배열 요소의 유형도 포인터 여야 함). 이것은 배열의 붕괴 특성으로 인해 작동합니다. 일단 붕괴되면 sizeof
더 이상 완전한 배열의 크기를 제공하지 않습니다. 그것이 본질적으로 포인터가되기 때문입니다. 그렇기 때문에 (다른 이유로) 참조 또는 포인터로 전달하는 것이 선호되는 이유입니다.
배열을 전달하는 세 가지 방법 1 :
void by_value(const T* array) // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])
마지막 두 개는 적절한 sizeof
정보를 제공하지만 첫 번째 것은 배열 인수가 붕괴되어 매개 변수에 할당 된 이후로는 그렇지 않습니다.
1 컴파일 할 때 상수 U를 알아야합니다.
T a[]
동일합니다 T *a
. 포인터 값이 이제 규정 된 것을 제외하고 by_pointer는 동일한 것을 전달합니다 const
. 배열 의 첫 번째 요소에 대한 포인터 가 아니라 배열에 포인터를 전달하려는 경우 구문은 T (*array)[U]
입니다.
a
의 배열입니다 char
다음, a
유형 인 char[N]
, 그리고에 붕괴 할 것이다 char*
; 그러나 &a
유형 char(*)[N]
이며 쇠퇴 하지 않습니다 .
U
변경 사항을 두 곳에서 변경하거나 자동 버그가 발생할 위험이있는 경우 ... 자율성!
배열은 기본적으로 C / C ++의 포인터와 동일하지만 완전히 다릅니다. 배열을 변환하면 :
const int a[] = { 2, 3, 5, 7, 11 };
포인터로 (캐스팅하지 않고 작동하므로 경우에 따라 예기치 않게 발생할 수 있음) :
const int* p = a;
sizeof
연산자가 배열의 요소를 계산 하는 능력을 상실합니다 .
assert( sizeof(p) != sizeof(a) ); // sizes are not equal
이 손실 능력을 "부패"라고합니다.
자세한 내용은이 기사에서 배열 붕괴에 대해 확인하십시오 .
표준의 내용은 다음과 같습니다 (C99 6.3.2.1/3-기타 피연산자-L 값, 배열 및 함수 지정자).
sizeof 연산자 또는 단항 & 연산자의 피연산자이거나 배열을 초기화하는 데 사용되는 문자열 리터럴 인 경우를 제외하고 유형 ''array of type ''이있는 표현식은 ''pointer to type 유형의 표현식으로 변환됩니다. 배열 객체의 초기 요소를 가리키고 lvalue가 아닌 type ''입니다.
즉, 배열 이름이 식에 사용될 때마다 배열의 첫 번째 항목에 대한 포인터로 자동 변환됩니다.
함수 이름은 비슷한 방식으로 작동하지만 함수 포인터는 배열 이름을 포인터로 자동 변환하는 것만 큼 혼동을 일으키지 않도록 훨씬 덜 특수하게 사용됩니다.
C ++ 표준 (4.2 배열에서 포인터로의 변환)은 변환 요구 사항을 (강조 광산)으로 완화합니다.
"NT의 배열"또는 "T의 알려지지 않은 범위의 배열"유형의 lvalue 또는 rvalue는 "pointer to T"유형의 rvalue로 변환 될 수 있습니다.
따라서 변환은 C에서 거의 항상 수행되는 것처럼 수행 될 필요 가 없습니다 (이는 함수 오버로드 또는 템플릿이 배열 유형에서 일치하도록합니다).
이것이 C에서 함수 프로토 타입 / 정의에서 배열 매개 변수를 사용하지 않아야하는 이유입니다 (제 의견으로는 일반적인 동의가 있는지 확실하지 않습니다). 그것들은 혼란을 야기하고 어쨌든 허구입니다-포인터 매개 변수를 사용하면 혼란이 완전히 사라지지는 않지만 적어도 매개 변수 선언은 거짓말이 아닙니다.
char x[] = "Hello";
. 6 개의 원소 배열은 "Hello"
붕괴되지 않습니다. 대신 x
크기 를 가져오고 6
해당 요소는의 요소에서 초기화됩니다 "Hello"
.
"부패"는 배열 형식에서 포인터 형식으로 식을 암시 적으로 변환하는 것을 말합니다. 대부분의 컨텍스트에서 컴파일러는 배열 표현식을 볼 때 표현식 유형을 "T의 N- 요소 배열"에서 "포인터에서 T"로 변환하고 표현식의 값을 배열의 첫 번째 요소의 주소로 설정합니다. . 이 규칙의 예외는 배열이 sizeof
또는 &
연산자 의 피연산자 이거나 배열이 선언에서 초기화 자로 사용되는 문자열 리터럴 인 경우입니다.
다음 코드를 가정하십시오.
char a[80];
strcpy(a, "This is a test");
식은 a
"문자의 80 요소 배열"형식이고 식 "테스트는"입니다 (문자의 16 요소 배열) (C에서는 문자열 리터럴은 const 문자의 배열 임). 그러나에 대한 호출에서 strcpy()
expression은 피연산자 sizeof
또는 모두 &
이므로 피연산자 유형은 "포인터에서 문자로"암시 적으로 변환되며 값은 각 요소의 첫 번째 요소 주소로 설정됩니다. 무엇 strcpy()
프로토 타입에서 볼 수 있듯이 수신하면, 배열,하지만 포인터되지 않습니다 :
char *strcpy(char *dest, const char *src);
이것은 배열 포인터와 동일하지 않습니다. 예를 들면 다음과 같습니다.
char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;
모두 ptr_to_first_element
와 것은 ptr_to_array
같은이 값을 ; a의 기본 주소 그러나 유형이 다르고 아래와 같이 다르게 취급됩니다.
a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]
표현이 기억 a[i]
으로 해석됩니다 *(a+i)
(배열 형식이 포인터 형식으로 변환하는 경우에만 작동한다), 모두 있도록 a[i]
하고 ptr_to_first_element[i]
작업 같은. 식은 (*ptr_to_array)[i]
로 해석됩니다 *(*a+i)
. 발현 *ptr_to_array[i]
및 ptr_to_array[i]
컴파일러 경고 또는 상황에 따라 오류가 발생할 수 있습니다; 평가할 것으로 예상되는 경우 분명히 잘못된 일을 수행합니다 a[i]
.
sizeof a == sizeof *ptr_to_array == 80
다시 말하지만 배열이 피연산자 인 sizeof
경우 포인터 유형으로 변환되지 않습니다.
sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
is on your platform
ptr_to_first_element
char에 대한 간단한 포인터입니다.
"This is a test" is of type "16-element array of char"
a는 "15-element array of char"
? (길이 14 + 1, \ 0)
C의 배열에는 값이 없습니다.
객체의 값이 예상되지만 객체가 배열 인 경우에는 첫 번째 요소의 주소가 type과 함께 사용됩니다 pointer to (type of array elements)
.
함수에서 모든 매개 변수는 값으로 전달됩니다 (배열도 예외는 아닙니다). 함수에서 배열을 전달하면 "포인터로 붕괴"(sic)됩니다. 배열을 다른 것과 비교할 때 다시 "포인터로 붕괴"(sic); ...
void foo(int arr[]);
함수 foo는 배열의 값을 기대합니다. 그러나 C에서는 배열에 가치가 없습니다! 따라서 foo
배열의 첫 번째 요소의 주소를 대신 가져옵니다.
int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }
위의 비교에서 arr
값이 없으므로 포인터가됩니다. int에 대한 포인터가됩니다. 해당 포인터를 변수와 비교할 수 있습니다 ip
.
배열 인덱싱 구문에서 arr은 '포인터로 쇠퇴합니다'
arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */
배열이 포인터로 붕괴되지 않는 유일한 경우는 sizeof 연산자 또는 & 연산자 ( 'address of'연산자)의 피연산자이거나 문자 배열을 초기화하는 데 사용되는 문자열 리터럴 일 때입니다.
배열이 썩고 ;-)을 가리킬 때입니다.
실제로, 어딘가에 배열을 전달하려고하지만 포인터가 대신 전달되는 경우 (도대체 누가 당신을 위해 전체 배열을 전달할 것이기 때문에) 사람들은 가난한 배열이 포인터로 부패했다고 말합니다.
a + 1
.
배열 소멸은 배열이 함수에 매개 변수로 전달 될 때 포인터와 동일하게 취급된다는 것을 의미합니다.
void do_something(int *array) {
// We don't know how big array is here, because it's decayed to a pointer.
printf("%i\n", sizeof(array)); // always prints 4 on a 32-bit machine
}
int main (int argc, char **argv) {
int a[10];
int b[20];
int *c;
printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
do_something(a);
do_something(b);
do_something(c);
}
위의 두 가지 합병증이나 예외가 있습니다.
먼저 C 및 C ++에서 다차원 배열을 처리 할 때 첫 번째 차원 만 손실됩니다. 배열은 메모리에 연속적으로 배치되므로 컴파일러는 해당 메모리 블록에 대한 오프셋을 계산할 수있는 첫 번째 차원을 제외한 모든 것을 알아야합니다.
void do_something(int array[][10])
{
// We don't know how big the first dimension is.
}
int main(int argc, char *argv[]) {
int a[5][10];
int b[20][10];
do_something(a);
do_something(b);
return 0;
}
둘째, C ++에서는 템플릿을 사용하여 배열의 크기를 추론 할 수 있습니다. Microsoft는 strcpy_s 와 같은 C ++ 버전의 Secure CRT 함수에 이것을 사용하며, 비슷한 트릭을 사용 하여 배열의 요소 수 를 안정적으로 얻을 수 있습니다.
배열을 함수 인수로 전달하는 네 가지 방법이 있다고 생각하면 대담 할 수 있습니다. 또한 여기에 당신의 설명을위한 짧지 만 작동하는 코드가 있습니다.
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
using namespace std;
// test data
// notice native array init with no copy aka "="
// not possible in C
const char* specimen[]{ __TIME__, __DATE__, __TIMESTAMP__ };
// ONE
// simple, dangerous and useless
template<typename T>
void as_pointer(const T* array) {
// a pointer
assert(array != nullptr);
} ;
// TWO
// for above const T array[] means the same
// but and also , minimum array size indication might be given too
// this also does not stop the array decay into T *
// thus size information is lost
template<typename T>
void by_value_no_size(const T array[0xFF]) {
// decayed to a pointer
assert( array != nullptr );
}
// THREE
// size information is preserved
// but pointer is asked for
template<typename T, size_t N>
void pointer_to_array(const T (*array)[N])
{
// dealing with native pointer
assert( array != nullptr );
}
// FOUR
// no C equivalent
// array by reference
// size is preserved
template<typename T, size_t N>
void reference_to_array(const T (&array)[N])
{
// array is not a pointer here
// it is (almost) a container
// most of the std:: lib algorithms
// do work on array reference, for example
// range for requires std::begin() and std::end()
// on the type passed as range to iterate over
for (auto && elem : array )
{
cout << endl << elem ;
}
}
int main()
{
// ONE
as_pointer(specimen);
// TWO
by_value_no_size(specimen);
// THREE
pointer_to_array(&specimen);
// FOUR
reference_to_array( specimen ) ;
}
나는 이것이 C ++ 대 C의 우수성을 보여줄 것이라고 생각할 수도 있습니다.
물론 힙 할당, 예외 및 std :: lib가없는 매우 엄격한 프로젝트가 있습니다. C ++ 네이티브 배열 처리는 미션 크리티컬 언어 기능입니다.
int a[10]; int b(void);
다음+a
은 int 포인터이며+b
함수 포인터입니다. 참조를 수락하는 템플릿으로 전달하려는 경우 유용합니다.