포인터 붕괴에 대한 배열은 무엇입니까?


384

포인터 붕괴에 대한 배열은 무엇입니까? 배열 포인터와 관련이 있습니까?


73
거의 알려지지 않음 : 단항 더하기 연산자는 "감쇠 연산자"로 사용될 수 있습니다. int a[10]; int b(void);다음 +a은 int 포인터이며 +b함수 포인터입니다. 참조를 수락하는 템플릿으로 전달하려는 경우 유용합니다.
Johannes Schaub-litb

3
@litb-parens는 동일한 작업을 수행합니다 (예 : (a) 포인터로 평가되는 표현식이어야 함).
Michael Burr

21
std::decayC ++ 14에서 단항 +에 비해 배열을 부패시키는 덜 모호한 방법입니다.
legends2k

21
이 질문은 C와 C 태그 때문에 @ JohannesSchaub - litb는 ++, 내가 비록 명확히하고 싶습니다 +a+bC에서 합법적 ++는 C (에 불법 C11 6.5.3.3/1 "단항의 피연산자 +또는 -운영자는 있어야한다 산술 유형 ")
MM

5
@lege 맞습니다. 그러나 나는 그것이 단항 +의 트릭만큼 잘 알려지지 않았다고 생각합니다. 내가 언급 한 이유는 단순히 부패하기 때문이 아니라 재미있게 놀 수 있기 때문입니다.)
Johannes Schaub-litb

답변:


283

배열은 포인터로 "부패"한다고합니다. 로 선언 된 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를 알아야합니다.


8
첫 번째 가치는 어떻게 전달됩니까?
rlbond

10
by_value는 배열의 첫 번째 요소에 포인터를 전달합니다. 함수 매개 변수의 맥락에서는 T a[]동일합니다 T *a. 포인터 값이 이제 규정 된 것을 제외하고 by_pointer는 동일한 것을 전달합니다 const. 배열 의 첫 번째 요소에 대한 포인터 가 아니라 배열에 포인터를 전달하려는 경우 구문은 T (*array)[U]입니다.
John Bode

4
"해당 배열에 대한 명시적인 포인터"-이것은 올바르지 않습니다. 경우 a의 배열입니다 char다음, a유형 인 char[N], 그리고에 붕괴 할 것이다 char*; 그러나 &a유형 char(*)[N]이며 쇠퇴 하지 않습니다 .
Pavel Minaev

5
@FredOverflow : U변경 사항을 두 곳에서 변경하거나 자동 버그가 발생할 위험이있는 경우 ... 자율성!
궤도에서 가벼움 레이스

4
"값으로 배열을 전달하는 경우 실제로 수행하는 것은 포인터를 복사하는 것입니다."배열이 값, 기간으로 전달 될 수 없으므로 의미가 없습니다.
juanchopanza

103

배열은 기본적으로 C / C ++의 포인터와 동일하지만 완전히 다릅니다. 배열을 변환하면 :

const int a[] = { 2, 3, 5, 7, 11 };

포인터로 (캐스팅하지 않고 작동하므로 경우에 따라 예기치 않게 발생할 수 있음) :

const int* p = a;

sizeof연산자가 배열의 요소를 계산 하는 능력을 상실합니다 .

assert( sizeof(p) != sizeof(a) );  // sizes are not equal

이 손실 능력을 "부패"라고합니다.

자세한 내용은이 기사에서 배열 붕괴에 대해 확인하십시오 .


51
배열은 기본적으로 포인터와 동일 하지 않습니다 . 그들은 완전히 다른 동물입니다. 대부분의 상황에서, 배열을 처리 할 수 있는 것처럼 이 포인터가 있었고, 포인터를 처리 할 수 있는 것처럼 이 배열했지만, 가깝게 그의 그들이 얻을 수있다.
John Bode

20
@ 존, 내 부정확 한 언어를 용서해주십시오. 나는 긴 뒷이야기에 빠져들지 않고 답을 얻으려고 노력하고 있었고, "기본적으로 ... 그러나 그렇지 않다"는 대학에 들어온 것처럼 좋은 설명입니다. 관심있는 사람은 의견이 올 바르면보다 정확한 그림을 얻을 수 있다고 확신합니다.
시스템 일시

"캐스트없이 작동"은 유형 변환에 대해 말할 때 "암시 적으로 발생하는"과 동일 함을 의미합니다.
MM

47

표준의 내용은 다음과 같습니다 (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에서 함수 프로토 타입 / 정의에서 배열 매개 변수를 사용하지 않아야하는 이유입니다 (제 의견으로는 일반적인 동의가 있는지 확실하지 않습니다). 그것들은 혼란을 야기하고 어쨌든 허구입니다-포인터 매개 변수를 사용하면 혼란이 완전히 사라지지는 않지만 적어도 매개 변수 선언은 거짓말이 아닙니다.


2
" 'array of type'유형을 가진 표현식"이 "배열을 초기화하는 데 사용되는 문자열 리터럴"인 코드의 예는 무엇입니까?
개럿

4
@ 개렛 char x[] = "Hello";. 6 개의 원소 배열은 "Hello"붕괴되지 않습니다. 대신 x크기 를 가져오고 6해당 요소는의 요소에서 초기화됩니다 "Hello".
MM

30

"부패"는 배열 형식에서 포인터 형식으로 식을 암시 적으로 변환하는 것을 말합니다. 대부분의 컨텍스트에서 컴파일러는 배열 표현식을 볼 때 표현식 유형을 "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에 대한 간단한 포인터입니다.


1
아닌가 "This is a test" is of type "16-element array of char"a는 "15-element array of char"? (길이 14 + 1, \ 0)
chux-복원 Monica Monica

16

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'연산자)의 피연산자이거나 문자 배열을 초기화하는 데 사용되는 문자열 리터럴 일 때입니다.


5
"배열에는 가치가 없습니다"-무슨 의미입니까? 물론 배열은 가치가있다. 그것들은 객체이고, 포인터를 가질 수 있고, C ++에서 그것들에 대한 참조 등을 가질 수있다.
Pavel Minaev

2
엄밀히 말하면 C에서 "값"은 유형에 따라 객체의 비트를 해석하는 것으로 정의됩니다. 배열 유형으로 유용한 의미를 알아내는 데 어려움을 겪고 있습니다. 대신 포인터로 변환한다고 말할 수 있지만 배열의 내용을 해석하지 않고 위치를 가져옵니다. 당신이 얻는 것은 배열의 값이 아닌 포인터의 값 (그리고 주소)입니다 (이것은 "string"의 정의에 사용 된 "포함 된 아이템의 값의 순서"입니다). 즉, 포인터를 얻는 것을 의미 할 때 "배열의 가치"라고 말하는 것이 공정하다고 생각합니다.
Johannes Schaub-litb

어쨌든 약간의 모호함이 있다고 생각합니다. 개체의 값과 식의 값 ( "rvalue"와 같이). 후자의 방식으로 해석되면 배열 표현식에는 반드시 값이 있습니다. 이는 rvalue로 쇠퇴 한 결과이며 포인터 표현식입니다. 그러나 이전 방식으로 해석하면 물론 배열 객체에 유용한 의미가 없습니다.
Johannes Schaub-litb

1
작은 수정이 포함 된 문구의 경우 +1; 배열의 경우에는 트리플렛 일뿐 아니라 단지 [위치, 유형]입니다. 배열의 경우 세 번째 위치에 대해 다른 점이 있습니까? 나는 생각할 수 없다.
legends2k

1
@ legends2k : 나는 세 번째 위치를 배열로 사용하여 연립 만있는 특별한 경우를 피했다고 생각합니다. 아마도 [location, type, void ]가 더 좋았을 것입니다.
pmg

8

배열이 썩고 ;-)을 가리킬 때입니다.

실제로, 어딘가에 배열을 전달하려고하지만 포인터가 대신 전달되는 경우 (도대체 누가 당신을 위해 전체 배열을 전달할 것이기 때문에) 사람들은 가난한 배열이 포인터로 부패했다고 말합니다.


멋지게 말했다. 포인터로 부패하지 않는 멋진 배열이나 부패되지 않는 배열은 무엇입니까? C로 예제를 인용 할 수 있습니까? 감사.
Unheilig

@Unheilig, 확실히, 배열을 구조체에 진공 포장하고 구조체를 전달할 수 있습니다.
Michael Krelin-해커

"일"이 무슨 뜻인지 잘 모르겠습니다. 실제로 발생하는 것을 예상하면 예상대로 작동하지만 배열을 지나서 액세스 할 수는 없습니다. 그 행동은 (공식적으로 정의되지 않았지만) 유지됩니다.
Michael Krelin-해커

붕괴는 다른 곳에서 배열을 전달하지 않는 많은 상황에서 발생합니다 (다른 답변에서 설명한 것처럼). 예를 들면 다음과 같습니다 a + 1.
MM

3

배열 소멸은 배열이 함수에 매개 변수로 전달 될 때 포인터와 동일하게 취급된다는 것을 의미합니다.

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 함수에 이것을 사용하며, 비슷한 트릭을 사용 하여 배열의 요소 수 를 안정적으로 얻을 수 있습니다.


1
소멸은 단순히 배열을 함수에 전달하는 것이 아니라 다른 많은 상황에서 발생합니다.
MM

0

tl; dr : 정의한 배열을 사용할 때 실제로 첫 번째 요소에 대한 포인터를 사용하게됩니다.

그러므로:

  • 글을 쓰면 arr[idx]정말 말하는 것 *(arr + idx)입니다.
  • 함수는 배열을 매개 변수로 사용하지 않으며 배열 매개 변수를 지정하더라도 포인터 만 사용합니다.

이 규칙에 대한 정렬 예외 :

  • 고정 길이 배열을의 함수에 전달할 수 있습니다 struct.
  • sizeof() 포인터의 크기가 아니라 배열이 차지하는 크기를 제공합니다.

0

배열을 함수 인수로 전달하는 네 가지 방법이 있다고 생각하면 대담 할 수 있습니다. 또한 여기에 당신의 설명을위한 짧지 만 작동하는 코드가 있습니다.

#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 ++ 네이티브 배열 처리는 미션 크리티컬 언어 기능입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.