2017-04-25 3 views
2

왜 작동합니까?고정 크기 배열 동작에 대한 포인터

uint8_t array[4] = {1,2,3,4}; 
uint8_t* parray = array; 
uint8_t (*p1)[4] = (uint8_t (*)[4])&array; 
uint8_t (*p2)[4] = (uint8_t (*)[4])&parray; 
uint8_t (*p3)[4] = (uint8_t (*)[4])parray; 
uint8_t test1 = **p1; // test1 = 1 
uint8_t test2 = **p2; // test2 = something random 
uint8_t test3 = **p3; // test3 = 1 

parray 분명 거의 배열과 동일하다. 예를 들어, array [0] == parray [0]. 그러나 배열 포인터를 고정 크기 배열에 대한 포인터로 가져 오려면 & 기호를 사용해야합니다. 포인터에 대한 포인터를 얻으려고하면 안됩니다.

실용 예제.

내가 포인터로 다른 함수에서 PARAM을 얻을 때 고정 크기 배열

void foo(uint8_t (*param)[4]) 
{ 
    ... 
} 

에 대한 포인터를 받아들이는 기능은 내가 foo는이 방법으로 전달할 수있다?

void bar(uint8_t param*) 
{ 
    uint8_t (*p)[4] = (uint8_t (*)[4])param; 
    foo(p); 
} 

더 좋은 방법이 있습니까?

+2

을 사용할 수 있습니다. 아마도 당신이 마음에서 지울 필요가있는 첫 번째 것은 너무 많은 입문 텍스트에서 영속되는 거짓말 일 것입니다. 포인터와 배열은 "거의 동일"합니다. 그들은 그렇지 않습니다. 어떤 상황에서는 그것들을 사용하는 표현이 같은 결과를 낳습니다. 그래서 때때로 거짓말이 사실로 보입니다. 그러나, 당신은 거짓말이 진정 거짓말 인 방식으로 그들을 사용하고 있습니다. – Peter

+0

'foo'가'uint8_t (*) [4]'형식의 인수를 사용하는 이유는 무엇입니까? –

+0

@DavidBowling 필자는'bar'가'foo'를 호출 할 때'uint_8 * '을 취하는 이유에 더 흥미를 느낍니다. – user2079303

답변

7

이 기능은 어레이 붕괴이라고하는 기능입니다. 변수 이름은 값 컨텍스트에서 변수 이름이 사용될 때 의 첫 번째 요소에 대한 포인터로의 부패이라고합니다.

배열은 값 컨텍스트에서 사용됩니다 : parray = array, 그래서 그것은 쇠퇴합니다. 감쇠량을 명시 적으로 쓸 수 있습니다 : parray = &(array[0]). 전 (내재적 붕괴)은 후자에 대한 통사론적인 설탕이다.

주소 연산자의 피연산자는 값 컨텍스트가 아닙니다. 따라서 배열 이름은 부패하지 않습니다. &array&(array[0])과 다릅니다. 첫 번째는 배열 유형의 주소를 취하고, 후자는 요소 유형의 주소를 취합니다. 반면에 parray은 완전히 다른 변수이고 &parray은 포인터가 저장된 주소를 반환합니다.이 주소는 배열이 저장된 주소가 아닙니다. &array 이미 유형 uint8_t (*)[4]의 때문에 변환이 중복 있지만

uint8_t (*p1)[4] = (uint8_t (*)[4])&array; 

이 올바른지

.

uint8_t (*p2)[4] = (uint8_t (*)[4])&parray; 

이것은 잘못된 것입니다. parrayuint8_t*이고 저장되는 주소에는 uint8_t[4] 유형의 개체가 없습니다. 대신 포인터가 들어 있습니다.

uint8_t (*p3)[4] = (uint8_t (*)[4])parray; 

이 약간 모호한이다. parrayuint8_t[4]에 대한 포인터가 아닌 uint8_t에 대한 포인터입니다. 그러나 주소가 uint8_t[4] 인 주소를 가리키는 경우가 발생하므로이 방법이 유용합니다. 프로그램의 동작에 의해 입증


parray은 분명 배열

과 거의 동일하지만 분명히 정확히 동일하지 않습니다.

arrayuint8_t 요소들의 어레이이고,는 parrayarray의 첫번째 요소를 가리 uint8_t 포인터이다. 이 구별은 이해하는 것이 중요합니다.


결론 : 그것은 무엇 배열 부패 이해하는 것이 중요하고, 가장 중요하게 배열과 포인터 사이의 차이는 무엇입니까 : 명시 적 변환이 컴파일러에서 실수를 숨길 수 있습니다 - 당신이 할 수있는 그들을 피하기가. 내가 포인터로 다른 함수에서 PARAM을 얻을 때

, 나는이 방법을 foo는 그것을 전달할 수 있습니다 편집을 위해


?

만 당신이 uint8_t[4]의 첫 번째 요소에 그 param 점을 입증 할 수있는 경우. 이는 본질적으로 bar의 사전 조건입니다.

이 더 나은 방법이 있나요 :

그러나, 당신이 요구 사항을 전달하는 타입 시스템을 사용할 수있을 때, 언어 사전 조건에 의존하지하는 것이 좋습니다?

변경 bar의 매개 변수 유형, 사용자가 올바른 유형의 포인터를 전달하는 알 수 있도록 : 물론

void bar(uint8_t (*param)[4]) { 
    foo(param); 
} 

을,이 간단한 예제에서 중복 bar한다.

+0

질문을 수정했습니다. 이 "모호한"캐스트를 사용해도 될지, 설명하지 않을 수 있습니까? – valentin

+0

@valentin 편집을 참조하십시오. – user2079303

2

할당 문,

uint8_t (*p2)[4] = (uint8_t (*)[4])&parray; 

이후

uint8_t test2 = **p2; 

하고 엄격한 앨리어싱을 위반하는 것입니다.

&parrayuint8_t** 유형이므로 (uint8_t (*)[4]) 유형 (둘 다 호환 유형이 아님)으로 전송 중이며 대상 포인터를 역 참조하려고합니다. 이로 인해 undefined behavior이 발생합니다. §6.5/P7

객체는 저장된 값 만 하나 다음 유형 갖는다 좌변 식 액세스 가진다

관련, C11 장 : 88)

을 -

- 객체의 유효 유형과 호환되는 유형의 정규화 된 버전

,

- 서명 또는 서명 입력이 객체의 효율적인 형태에 대응하는 유형

- 서명 또는 서명 입력이 대상의 효과적인 유형의 정규화 된 버전에 대응하는 형식 ,

- 자 형 - 그 부재 중 상기 하나의 종류 (재귀 포함한 subaggregate 포함되거나 조합의 구성원) 또는

를 포함하는 전체 또는 조합 형태.

2

은 "parray 분명 배열과 거의 동일하다"< -이 부분이 잘못된

parray 유형, 예컨대 것과 array 유형에서 암시 적 변환이 존재 초기화하는 (또는 할당), 예. uint8_t* parray = array;parray&array[0]과 같게 설정합니다. 반대 방향으로 변환이 존재하지 않습니다. p1 p2 p3 당신의 초기화에

, 당신은 당신의 캐스트 여기

uint8_t (*p1)[4] = (uint8_t (*)[4])&array; 

캐스트가 불필요, &array와 표현의 유형을 마스킹하는 것은 이미 캐스팅이 여기에 (uint8_t (*)[4])

uint8_t (*p2)[4] = (uint8_t (*)[4])&parray; 

입니다 거짓말, &parrayuint8_t**

uint8_t (*p3)[4] = (uint8_t (*)[4])parray; 

여기 캐스트는 때문에 parray

0

의 값으로 안전하지만은 고정 된 크기 배열에 대한 포인터와 배열에 대한 포인터를 얻고 싶은 경우에, 나는 & 기호를 사용해야합니다.

여기에 귀하의 진술은 특별한 경우에 완전히 잘못되었거나 전반적으로 잘못되었습니다. 당신은 이미 그것을 얻었고 포인터 변수 parray에 할당 된 다음 배열에 대한 포인터로서 그 변수의 주소를 처리하려고 시도 했습니까? 특히 배열의 이름은 배열을 가리키는 포인터가됩니다. 일반적으로 배열에 대한 포인터는 배열의 첫 번째 요소에 대한 포인터와 동일하므로 &array[0] 또는 단지 array