programing

포인터 붕괴에 대한 배열이란?

prostudy 2022. 4. 16. 09:41
반응형

포인터 붕괴에 대한 배열이란?

포인터 붕괴에 대한 배열이란?어레이 포인터와 관련이 있는가?

배열은 "디케이"하여 포인터를 만든다고 한다.다음과 같이 선언된 C++ 배열int numbers [5]재탕할 수 없다, 다시 말해서 말할 수 없다numbers = 0x5a5aff23더 중요한 것은 붕괴라는 용어는 유형과 치수의 손실을 의미한다.numbers로 타락하다.int*치수 정보(카운트 5)를 잃어버리고 유형이 그렇지 않음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는 컴파일 시간에 알 수 있어야 한다.

배열은 기본적으로 C/C++의 포인터와 동일하지만 완전히는 아니다.어레이를 변환하는 경우:

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

포인터(주물 없이 작동하므로 어떤 경우에는 예기치 않게 발생할 수 있음):

const int* p = a;

당신은 그 능력을 잃는다.sizeof배열에서 요소 수를 세는 연산자:

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

이 능력을 잃어버린 것을 "decay"라고 부른다.

자세한 내용은 어레이 붕괴에 대한기사를 참조하십시오.

표준에 따르면 다음과 같다(C99 6.3.2.1/3 - 기타 피연산자 - Lvalues, 배열 및 함수 지정자:

연산자 또는 단항 및 연산자 크기의 피연산자이거나 배열을 초기화하는 데 사용되는 문자열 리터럴인 경우를 제외하고, "형식의 배열"을 가진 표현식은 배열 객체의 초기 요소를 가리키고 l값이 아닌 "형식"을 가진 표현식으로 변환된다.

이는 배열 이름이 표현식에 사용될 때마다 배열의 첫 번째 항목에 대한 포인터로 자동 변환된다는 것을 의미한다.

함수 이름은 유사한 방식으로 작용하지만 함수 포인터는 훨씬 덜 사용되며 훨씬 더 전문화된 방식으로 어레이 이름을 포인터로 자동 변환하는 것만큼 큰 혼동을 일으키지 않는다는 점에 유의하십시오.

C++ 표준(4.2 Array-to-pointer 변환)은 변환 요구 사항을 (강제 광산)로 느슨하게 한다.

"N T 배열" 또는 "알 수 없는 경계 T 배열" 유형의 lvalue 또는 rvalue를 "pointinter to T" 유형의 rvalue로 변환할 수 있다.

따라서 변환은 항상 C에서와 같이 수행될 필요가 없다(이것은 함수의 오버로드 또는 템플릿이 어레이 유형과 일치하도록 한다).

C에서 기능 프로토타입/정의에 어레이 파라미터를 사용하는 것을 피해야 하는 이유도 여기에 있다(내 생각에는 - 일반적인 합의가 있는지는 잘 모르겠다).그들은 혼란을 일으키고 어쨌든 허구다 - 포인터 매개변수를 사용하고 혼란이 완전히 사라지지는 않을 수도 있지만, 적어도 매개변수 선언이 거짓은 아니다.

"Decay"는 표현식을 배열 형식에서 포인터 형식으로 암묵적으로 변환하는 것을 말한다.대부분의 맥락에서 컴파일러는 배열 식을 볼 때 식 유형을 "N-element array of T"에서 "pointer to T"로 변환하고 식 값을 배열의 첫 번째 요소의 주소로 설정한다.이 규칙의 예외는 배열이 다음 중 하나의 피연산자인 경우 입니다.sizeof또는&연산자 또는 배열은 선언에서 이니셜라이저로 사용되는 문자열 리터럴이다.

다음 코드를 가정해 보십시오.

char a[80];
strcpy(a, "This is a test");

은 그랬다.a"80-element array of char" 형식이고 "Test is a test" 형식은 "15-element array of char" 형식이다(C: C++ 문자열 리터럴은 const char 배열이다).그러나, 로의 호출에서.strcpy(), 어느 표현도 의 피연산자가 아니다.sizeof또는&, 그래서 그들의 유형은 암묵적으로 "to char"로 변환되고, 그들의 값은 각각에 있는 첫 번째 요소의 주소로 설정된다.무엇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_arraya의 기본 주소와 이 같다.단, 아래와 같이 종류도 다르고 취급도 다르다.

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에 대한 포인터야.

배열(C)은 가치가 없다.

객체의 값이 예상되지만 객체가 배열인 경우, 첫 번째 요소의 주소가 대신 사용되며, 형식은 다음과 같다.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가치가 없어서 포인터가 된다.그것은 인트로 가는 포인터가 된다.그 포인터를 변수와 비교할 수 있다.ip.

자주 볼 수 있는 배열 인덱싱 구문에서는 arr이 '포인터로 변경됨'으로 표시됨

arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */

배열이 포인터로 분해되지 않는 유일한 경우는 연산자 크기, 연산자 & 연산자('주소' 연산자) 또는 문자 배열을 초기화하는 데 사용되는 문자열 리터럴일 때뿐이다.

어레이가 회전하여;-)를 가리키고 있을 때

사실, 단지 어딘가에 배열을 통과하고 싶어도 그 대신 포인터가 통과된다면(왜냐하면 누가 배열을 통째로 통과하겠는가), 사람들은 배열이 불량해 포인터로 전락했다고 말한다.

배열 부패는 배열이 함수에 매개변수로 전달될 때 포인터와 동등하게 처리됨("결정")을 의미한다.

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 기능에 사용하며, 유사한 기술을 사용하여 어레이의 요소 수를 안정적으로 얻을 수 있다.

tl;dr: 정의한 배열을 사용할 경우 실제로 첫 번째 요소에 대한 포인터를 사용할 수 있다.

따라서 다음과 같다.

  • arr[idx]넌 정말 그냥 말하는 거야.*(arr + idx).
  • 함수는 실제로 어레이를 매개 변수로 사용하지 않으며 포인터만 포함되며, 어레이 매개 변수를 지정할 때 직접 또는 어레이에 참조를 전달할 경우 간접적으로만 해당된다.

이 규칙의 예외 정렬:

  • 고정 길이 어레이를 a 내의 함수에 전달할 수 있음struct.
  • sizeof()포인터 크기가 아니라 배열에서 차지하는 크기를 제공한다.

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

#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의 우월성을 보여준다고 생각할지도 모른다.적어도 참조로 배열을 전달하는 참조(pun 의도된)에서.

물론 힙 할당, 예외 및 std: lib가 없는 매우 엄격한 프로젝트도 있다.C++ 네이티브 어레이 처리 기능은 미션 크리티컬 언어 기능이라고 할 수 있다.

배열은 C에서 포인터를 통해 자동으로 전달된다.그 이면의 근거는 추측만 할 수 있을 뿐이다.

int a[5] int *a그리고int (*a)[5]모두 미화된 주소로서, 컴파일러는 자신에 대한 산술 및 경의 연산자를 종류에 따라 다르게 취급하므로, 그들이 같은 주소를 참조할 때 컴파일러에 의해 동등하게 취급되지 않는다. int a[5]주소가 함축되어 있고 배열 자체의 일부로 스택이나 실행 파일에 나타나지 않는다는 점에서 다른 2와 다르며, 그것은 컴파일러에 의해서만 주소나 포인터 산술과 같은 특정한 산술 연산을 해결하기 위해 사용된다. int a[5]그러므로 묵시적 주소일 뿐 아니라 배열이지만, 주소 자체에 대해 말하고 스택에 배치하는 순간, 주소 자체는 더 이상 배열이 아니며, 배열에 대한 포인터 또는 썩은 배열에 대한 포인터(즉, 배열의 첫 번째 멤버의 첫 번째 멤버에 대한 포인터일 뿐이다.

를 들어, 예를 , on.int (*a)[5] , , 번째 불합격에 첫 불합격.a을 산출할 것이다.int *(따라서 동일한 주소, 단지 다른 유형, 그리고 메모가 없음)int a[5]에 ()을 붙인다.aa+1또는*(a+1)이것은 데이터 으로 할 이며, 두 5 ints의 ints를 생산하게 될 이다.int. on.int a[5] 첫 부결은 나 첫 은을 하게 될 것이다.int 포인터 는 '그들리'의 이다.int.

함수에 전달만 가능int *그리고int (*)[5]그리고 함수는 매개변수 유형이 무엇이든 그것을 캐스팅하기 때문에 함수 내에서 당신은 썩은 배열로 전달되는 주소를 처리할 것인지 아니면 배열로 전달되는 포인터(함수가 전달할 배열의 크기를 지정해야 하는 곳)로 처리할 것인지 선택할 수 있다.합격하면a어떤 기능까지 그리고a정의됨int a[5], 그러면 as.a주소를 결정하여 주소를 전달하고 있으며 주소는 포인터 유형만 될 수 있다.함수에서, 그것이 접근하는 매개변수는 스택의 주소나 레지스터의 주소로서, 이것은 포인터 타입일 뿐 배열 타입이 될 수 없다. 이는 스택의 실제 주소이기 때문에 어레이 자체가 아니기 때문이다.

사용할 때 볼 수 있듯이 주소인 매개 변수의 유형이 포인터일 뿐 배열 크기가 없기 때문에 배열 크기가 손실됨sizeof에 전달되는 값의 종류에 따라 작용한다.매개 변수 유형int a[5]대신에int *a허용되지만 로 취급된다.int *그것을 노골적으로 허용하지 않는 대신, 비록 그것이 허용되어서는 안 되겠지만, 그것은 오해의 소지가 있기 때문에, 그것은 크기 정보를 사용할 수 있다고 생각하게 하지만, 단지 그것을 던져야만 이 일을 할 수 있기 때문이다.int (*a)[5]그리고 물론 함수는 배열의 크기가 컴파일 시간 상수여야 하기 때문에 배열의 크기를 전달할 방법이 없기 때문에 배열의 크기를 지정해야 한다.

이 코드 사용


void f(double a[10]) {
    printf("in function: %d", sizeof(a));
    printf("pointer size: %d\n", sizeof(double *));
}

int main() {
    double a[10];
    printf("in main: %d", sizeof(a));
    f(a);
}

그러면 함수 내부의 배열 크기가 주 배열의 크기와 같지는 않지만 포인터 크기와 같다는 것을 알 수 있을 것이다.

여러분은 아마 "어레이는 포인터"라고 들었을 것이다. 하지만, 이것은 정확히 사실이 아니다.sizeof안쪽에main올바른 크기의 인쇄를 하다.그러나 통과되면 배열은 포인터로 디코딩된다.즉, 구문이 무엇을 보여주든 간에 실제로 포인터를 통과하게 되고, 함수는 실제로 포인터를 받게 된다.

는 경의의 것이다void f(double a[10]컴파일러에 의해 은연중에 로 변형되어void f(double *a). 함수 인수를 다음과 같이 직접 선언할 수 있었다.*a너는 심지어 글을 쓸 수도 있었다.a[100]또는a[1]대신에a[10], 실제로 그런 식으로 편찬된 적이 없기 때문에(하지만, 명백하게 해서는 안 되며, 그것은 독자를 혼란스럽게 할 것이다.

참조URL: https://stackoverflow.com/questions/1461432/what-is-array-to-pointer-decay

반응형