이 방법으로 숫자의 제곱을 계산할 수 없습니다


135

숫자의 제곱을 계산하는 함수를 찾았습니다.

int p(int n) {
    int a[n]; //works on C99 and above
    return (&a)[n] - a;
}

n 2의 값을 반환합니다 . 질문은, 어떻게합니까? 약간의 테스트 후, 나는 사이에 발견 (&a)[k]하고 (&a)[k+1]있다 sizeof(a)/ sizeof(int). 왜 그런 겁니까?


6
이 정보를 찾은 곳으로 연결되는 링크가 있습니까?
R Sahu

4
int p(n)? 컴파일 되나요?
barak manos

78
이것은 굉장합니다. 이제는 다시 사용하지 않고 n * n을 대신 사용하십시오.

26
또는 더 나은 :int q(int n) { return sizeof (char [n][n]); }
ouah

17
@ouah 가이 질문을 가정하면 codegolf.stackexchange.com/a/43262/967을 사용하지 않는 이유 sizeof는 문자를 저장 하는 것이 었습니다. 다른 사람 : 이것은 의도적으로 모호한 코드이며 정의되지 않은 동작이며 @ouah의 대답은 정확합니다.
ecatmur

답변:


117

분명히 해킹 ...하지만 *연산자 를 사용하지 않고 숫자를 제곱하는 방법입니다 (코딩 컨테스트 요구 사항이었습니다).

(&a)[n] 

int위치를 가리키는 포인터와 같습니다.

(a + sizeof(a[n])*n)

따라서 전체 표현은

  (&a)[n] -a 

= (a + sizeof(a[n])*n -a) /sizeof(int)

= sizeof(a[n])*n / sizeof(int)
= sizeof(int) * n * n / sizeof(int)
= n * n

11
그리고 당신이 분명히 암시하지만 명시 적으로 표현할 필요가 있다고 느끼면, 그것은 가장 좋은 구문 해킹입니다. 곱하기 연산은 여전히 ​​그 아래에있을 것입니다. 피하는 것은 운영자 일뿐입니다.
Tommy

나는 그것이 뒤에서 일어나는 것을 얻었지만 내 실제 질문은 왜 (& a) [k]가 + k * sizeof (a) / sizeof (int)와 같은 주소에 있는지입니다.
Emanuel

33
오래된 코더로서 컴파일러가 컴파일 타임에 알 수없는 시기 (&a)의 객체에 대한 포인터로 취급 할 수 있다는 사실에 반했습니다 . C는 단순한 언어 n*sizeof(int)n
Floris

그것은 꽤 영리한 해킹이지만 생산 코드에서 볼 수없는 것입니다.
John Odom

14
옆으로, 그것은 또한 기본 배열의 요소를 가리 키거나 과거를 가리 키지 않도록 포인터를 증가시키기 때문에 UB입니다.
중복 제거기

86

이 해킹을 이해하려면 먼저 포인터 차이를 이해해야합니다. 즉, 동일한 배열의 요소를 가리키는 두 포인터 를 빼면 어떻게됩니까?

한 포인터를 다른 포인터에서 빼면 결과는 포인터 간의 거리 (배열 요소로 측정)입니다. 그래서, 경우 p에 점 a[i]q점하기 위해 a[j], 다음 p - q과 같다i - j .

C11 : 6.5.6 가산 연산자 (p9) :

두 개의 포인터를 빼면 둘 다 같은 배열 객체의 요소 또는 배열 객체의 마지막 요소를 지나는 요소를 가리켜 야합니다. 결과는 두 배열 요소의 아래 첨자의 차이입니다 . [...].
즉, 식 경우 PQ포인트 각각 i번째 및 j배열 객체의 번째 요소 식은 (P)-(Q)값 갖는i−j 유형의 객체에 맞는 값을 제공 ptrdiff_t.

이제 배열 이름을 포인터로 a변환하고 포인터를 배열의 첫 번째 요소로 변환하는 것을 알고 a있습니다. &a는 전체 메모리 블록의 주소입니다 a. 즉, 배열의 주소입니다 . 아래 그림은 이해하는 데 도움이됩니다 ( 자세한 설명은 이 답변읽으십시오 ).

여기에 이미지 설명을 입력하십시오

이렇게하면 왜 a, 왜 &a같은 주소를 가지고 있고 (&a)[i]i 번째 배열 의 주소가 어떻게 주소와 같은지 이해하는 데 도움이됩니다 a.

그래서, 진술

return (&a)[n] - a; 

에 해당

return (&a)[n] - (&a)[0];  

이 차이는 포인터 (&a)[n]와 사이의 요소 수를 제공하며 각 요소 (&a)[0]n배열입니다 n int. 따라서 총 배열 요소는 n*n= n2 입니다.


노트:

C11 : 6.5.6 가산 연산자 (p9) :

두 개의 포인터를 빼면 둘 다 같은 배열 객체의 요소를 가리 키거나 하나는 배열 객체의 마지막 요소를 지나야합니다 . 결과는 두 배열 요소의 아래 첨자의 차이입니다. 결과의 크기는 implementation-defined 이며 해당 유형 (부호있는 정수 유형)이 헤더에 ptrdiff_t정의됩니다 <stddef.h>. 해당 유형의 객체에서 결과를 표현할 수없는 경우 동작이 정의되지 않습니다.

이후 (&a)[n]어레이 오브젝트의 마지막 요소 과거 동일한 배열 오브젝트 나 하나의 요소도 포인트 (&a)[n] - a호출한다 정의되지 않은 동작 .

또한 함수의 반환 형식을 변경하는 것이 더 나은, 그주의 pptrdiff_t.


"둘 다 동일한 배열 객체의 요소를 가리켜 야합니다."-이 "핵"이 UB가 아닌지에 대한 의문이 제기됩니다. 포인터 산술 표현식은 존재하지 않는 객체의 가상 끝을 나타냅니다.
Martin Ba

요약하면, a는 n 개의 요소 배열의 주소이므로 & a [0]은이 배열에서 첫 번째 요소의 주소이며 a와 같습니다. 또한 & a [k]는 k에 관계없이 항상 n 개의 요소 배열의 주소로 간주되며 & a [1..n]도 벡터이므로 요소의 "위치"는 연속적입니다. 첫 번째 요소는 x 위치에 있고, 두 번째는 x + 위치에 있습니다 (벡터 a의 요소 수는 n입니다). 내가 맞아? 또한 이것은 힙 공간이므로 동일한 n 요소의 새 벡터를 할당하면 주소가 (& a) [1]과 동일합니까?
Emanuel

1
@ 에마누엘; array 요소의 &a[k]주소입니다 . 그것은는 항상 배열의 주소로 간주됩니다 요소. 따라서, 첫 번째 요소는 위치에 (또는 , 2 위치에있다) (배열의 요소 번호 + 이다 (어레이 요소의 크기) *) 등. 가변 길이 배열의 메모리는 힙이 아닌 스택에 할당됩니다. ka(&a)[k]ka&aaan
하크

@ 마틴 바; 이것도 허용됩니까? 아니요. 허용되지 않습니다. UB. 편집 내용을 참조하십시오.
haccks

1
@haccks 질문과 자연의 닉네임 사이의 우연의 일치
Dimitar Tsonev

35

a의 (변수) 배열입니다 n int.

&a의 (변수) 배열에 대한 포인터입니다 n int.

(&a)[1]의 포인터 intint마지막 배열 요소 과거. 이 포인터는 n int이후의 요소 &a[0]입니다.

(&a)[2]의 포인터를 intint두 배열의 마지막 배열 요소 과거. 이 포인터는 2 * n int이후의 요소 &a[0]입니다.

(&a)[n]의 포인터를 int하나 int의 마지막 배열 요소 과거의 n배열. 이 포인터는 n * n int이후의 요소 &a[0]입니다. 그냥 빼기 &a[0]또는 a당신은이 n.

물론 이것은 (&a)[n]배열 내부 또는 마지막 배열 요소를 지나지 않는 (포인터 산술의 C 규칙 에 따라) 시스템에서 작동하더라도 기술적으로 정의되지 않은 동작입니다 .


글쎄요, 그런데 왜 C에서 이런 일이 발생합니까? 이것의 논리는 무엇입니까?
Emanuel

@Emanuel 실제로 포인터 산술이 거리를 측정하는 데 유용하다는 것 (보통 배열에서)보다 [n]구문 에 대한 엄격한 대답은 없습니다. 구문은 배열을 선언하고 배열은 포인터로 분해됩니다. 이 결과로 세 가지 유용한 것들이 있습니다.
Tommy

1
@Emanuel 누군가가 이것을 할 것인지 묻는 다면 , 행동의 UB 특성으로 인한 이유와 모든 이유가 거의 없습니다 . 그리고 그것은 가치가 그 지적이다 (&a)[n]유형 int[n], 그리고 와 같은 표현 int*설명에 취소되지 않은 경우에는 자신의 첫 번째 요소의 주소로 표현하는 배열로 인해이.
WhozCraig

아니, 나는 왜 누군가가 이것을 할 것인지를 의미하지 않았다. 나는 왜이 상황에서 C 표준이 이런 식으로 행동 하는지를 의미했다.
Emanuel

1
@Emanuel Pointer Arithmetic (이 경우 해당 주제의 하위 장 : pointer differenceencing ). 이 사이트에서 질문과 답변을 읽는 것뿐만 아니라 인터넷 검색도 가치가 있습니다. 많은 유용한 이점이 있으며 올바르게 사용될 때 표준에 구체적으로 정의되어 있습니다. 완전히 파악하기 위해서는 당신 어떻게 '이해하는 유형 이 나열된 코드가 고안되어있다.
WhozCraig

12

동일한 배열의 두 요소를 가리키는 두 개의 포인터가 있으면 그 차이로 인해 이러한 포인터 사이의 요소 수가 산출됩니다. 예를 들어이 코드 스 니펫은 2를 출력합니다.

int a[10];

int *p1 = &a[1];
int *p2 = &a[3];

printf( "%d\n", p2 - p1 ); 

이제 표현을 고려하자

(&a)[n] - a;

이 표현 a에서 유형은int * 있으며 첫 번째 요소를 가리 킵니다.

표현식 &a에는 유형이 int ( * )[n]있으며 이미지화 된 2 차원 배열의 첫 번째 행을 가리 킵니다. a유형은 다르지만 값이 일치합니다 .

( &a )[n]

이 이미지화 된 2 차원 배열의 n 번째 요소이고 유형 int[n]은 이미지 배열의 n 번째 행입니다. 표현(&a)[n] - a 첫 번째 요소의 주소로 변환되며`int * 유형을 갖습니다.

그래서 사이 (&a)[n]an 개의 요소의 행이 없음. 따라서 그 차이는 같습니다 n * n.


따라서 모든 배열 뒤에는 크기가 n * n 인 행렬이 있습니까?
Emanuel

@Emanuel이 두 포인터 사이에는 nxn 요소의 행렬이 있습니다. 그리고 포인터의 차이는 포인터 사이의 요소 수인 n * n과 같은 값을 제공합니다.
모스크바에서 Vlad

그러나 왜이 행렬의 크기가 n * n 뒤에 있습니까? C에서 사용합니까? 내가 모르는 사이에, 크기 n의 더 많은 배열을 C "할당 된"것과 같습니까? 그렇다면 사용할 수 있습니까? 그렇지 않으면, 왜이 행렬이 형성 되는가 (즉, 거기에 목적이 있어야 함).
Emanuel

2
@Emanuel-이 행렬은이 경우 포인터 산술의 작동 방식에 대한 설명 일뿐입니다. 이 매트릭스는 할당되지 않았으므로 사용할 수 없습니다. 이미 몇 번 언급 한 것처럼 1)이 코드 스 니펫은 실용적이지 않은 해킹입니다. 2)이 해킹을 이해하려면 포인터 산술이 어떻게 작동하는지 알아야합니다.
void_ptr

@Emanuel 이것은 포인터 산술을 설명합니다. Expreesion (& a) [n]은 포인터 산술로 인해 이미지화 된 2 차원 배열의 n- 요소를 가리키는 포인터입니다.
모스크바에서 Vlad

4
Expression     | Value                | Explanation
a              | a                    | point to array of int elements
a[n]           | a + n*sizeof(int)    | refer to n-th element in array of int elements
-------------------------------------------------------------------------------------------------
&a             | a                    | point to array of (n int elements array)
(&a)[n]        | a + n*sizeof(int[n]) | refer to n-th element in array of (n int elements array)
-------------------------------------------------------------------------------------------------
sizeof(int[n]) | n * sizeof(int)      | int[n] is a type of n-int-element array

그러므로,

  1. 의 유형 (&a)[n]int[n]포인터
  2. 의 유형 aint포인터

이제 표현식 (&a)[n]-a은 포인터 빼기를 수행합니다.

  (&a)[n]-a
= ((a + n*sizeof(int[n])) - a) / sizeof(int)
= (n * sizeof(int[n])) / sizeof(int)
= (n * n * sizeof(int)) / sizeof(int)
= n * n
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.