음의 배열 인덱스가 의미가있는 이유는 무엇입니까?


14

C 프로그래밍에서 이상한 경험을했습니다. 이 코드를 고려하십시오.

int main(){
  int array1[6] = {0, 1, 2, 3, 4, 5};
  int array2[6] = {6, 7, 8, 9, 10, 11};

  printf("%d\n", array1[-1]);
  return 0;
}

컴파일하고 실행할 때 오류나 경고가 표시되지 않습니다. 강사가 말했듯이 배열 인덱스 -1는 다른 변수에 액세스합니다. 여전히 혼란 스럽습니다. 왜 지구상에서 프로그래밍 언어에이 기능이 있습니까? 음의 배열 인덱스를 허용하는 이유는 무엇입니까?


2
이 질문은 C를 사용하여 구체적인 프로그래밍 언어로 동기를 부여 받았지만 여기서는 거의 주제가 아닌 개념적 질문으로 이해 될 수 있다고 생각합니다.
Raphael

7
나는 동의하지 @Raphael하고 SO에 속하는 것으로 판단, 어느 쪽이 교과서 정의되지 않은 동작 (배열 외부 참조 메모리) 및 이것에 대해 경고해야 적절한 컴파일러 플래그입니다
래칫 괴물

@ratchetfreak에 동의합니다. 유효한 인덱스 범위가 [0, 5]이므로 컴파일러 결함 인 것 같습니다. 외부에있는 것은 컴파일 / 런타임 오류 여야합니다. 일반적으로 벡터는 첫 번째 요소 인덱스가 사용자에게 달려있는 함수특정 경우입니다 . C 계약은 요소가 인덱스 0에서 시작하므로 부정적인 요소에 액세스하는 것은 오류입니다.
Val

2
@Raphael C는 여기에서 중요한 배열을 가진 일반적인 언어에 비해 두 가지 특징이 있습니다. 하나는 C에 하위 -1배열이 있고 하위 배열의 요소를 참조하는 것이 더 큰 배열에서 해당 배열 이전의 요소를 참조하는 데 유효한 방법입니다. 다른 하나는 인덱스가 유효하지 않으면 프로그램이 유효하지 않지만 대부분의 구현에서는 범위를 벗어난 오류가 아니라 조용한 나쁜 동작을 보게된다는 것입니다.
Gilles 'SO- 악의를 멈춰라'

4
@Gilles 그것이 문제의 핵심이라면, 이것이 실제로 스택 오버플 로에 있었을 것입니다 .
Raphael

답변:


27

배열 인덱싱 연산 a[i]은 C의 다음 기능에서 의미를 얻습니다.

  1. 구문은와 a[i]같습니다 *(a + i). 따라서 5[a]의 5 번째 요소에 도달 하는 것은 유효합니다 a.

  2. 포인터 산술 포인터 주어진 것을 말한다 p및 정수 i, p + i 포인터 p에 의해 진행 i * sizeof(*p)바이트

  3. 배열의 이름은 a매우 빠르게 0 번째 요소에 대한 포인터로 이동합니다.a

실제로 배열 인덱싱은 포인터 인덱싱의 특별한 경우입니다. 포인터 모양처럼 그 배열 내부의 장소에 임의의 표현을 가리킬 수 있기 때문에 p[-1]입니다 하지 검사에 의해 잘못, 그리고 컴파일러는하지 않도록 오류 이러한 모든 표현을 고려 (수).

귀하의 예를 실제로 배열의 이름이 실제로 유효하지 않습니다. 식의 결과로서 의미 포인터 값이 있다면 IIRC, 그것은 정의되지 배열의 0 번째 요소에 대한 포인터로이 알고있다. 따라서 영리한 컴파일러는이를 감지하여 오류로 표시 할 수 있습니다. 임의의 스택 슬롯에 대한 포인터를 제공하여 다른 컴파일러가 여전히 호환 될 수 있습니다.a[-1]aa - 1a

컴퓨터 과학의 대답은 다음과 같습니다.

  • C에서 []연산자는 배열이 아닌 포인터에 정의됩니다. 특히, 포인터 산술 및 포인터 역 참조 측면에서 정의됩니다.

  • C에서 포인터는 추상적으로 튜플 (start, length, offset)입니다 0 <= offset <= length. 포인터 산술은 기본적으로 오프셋에서 산술 연산을 수행하며 연산 결과가 포인터 조건을 위반하면 정의되지 않은 값이라는 경고가 있습니다. 포인터를 참조 해제하면 추가 제약 조건이 추가 offset < length됩니다.

  • C는 undefined behaviour컴파일러가 튜플을 단일 숫자로 구체적으로 표현할 수 있는 개념을 가지고 있으며 포인터 조건의 위반을 감지 하지 않아도됩니다. 추상 의미론을 만족시키는 모든 프로그램은 구체적 (손실) 의미론으로 안전합니다. 추상 시맨틱을 위반하는 것은 주석없이 컴파일러에 의해 받아 들여질 수 있으며, 그것과 관련하여 원하는 모든 것을 할 수 있습니다.


특정 프로그래밍 언어의 특질에 의존하지 않고 일반적인 답변을 제공하십시오.
Raphael

6
@Raphael, 질문은 C에 관한 것이 었습니다. 저는 C 컴파일러가 C 의 정의 에서 겉보기에 의미가없는 표현을 컴파일 할 수있는 이유에 대한 구체적인 질문을 해결했다고 생각합니다 .
Hari

특히 C에 대한 질문은 여기서는 주제가 아닙니다. 질문에 대한 내 의견을 적어 둡니다.
Raphael

5
나는 질문의 비교 언어 적 측면이 여전히 유용하다고 믿는다. 특정 구현이 특정 콘크리트 의미를 나타내는 이유에 대해 상당히 "컴퓨터 과학"의 풍미있는 설명을했다고 생각합니다.
Hari

15

배열은 단순히 인접한 메모리 덩어리로 배치됩니다. a [i]와 같은 어레이 액세스는 메모리 위치 addressOf (a) + i 에 대한 액세스로 변환됩니다 . 이 코드 a[-1]는 완벽하게 이해할 수 있으며 단순히 배열이 시작되기 전에 주소를 나타냅니다.

이것은 미친 것처럼 보일 수 있지만 이것이 허용되는 많은 이유가 있습니다.

  • 인덱스 i에서 a [-]가 배열의 범위 내에 있는지 확인하는 것은 비용이 많이 듭니다.
  • 일부 프로그래밍 기술은 실제로 a[-1]유효한 사실을 이용 합니다. 예를 들어, 그것이 a실제로 배열의 시작이 아니라 배열의 중간에 대한 포인터 임을 알고 있다면 포인터 a[-1]의 왼쪽에있는 배열의 요소를 가져옵니다.

6
다시 말해서 아마도 사용해서는 안됩니다. 기간. 이름이 Donald Knuth이고 다른 17 개의 지침을 저장하려고합니까? 꼭 가십시오.
Raphael

답장을 보내 주셔서 감사하지만 아이디어를 얻지 못했습니다. BTW 나는 그것을 이해할 때까지 계속해서 다시 읽을 것이다 .. :)
Mohammed Fawzan

2
@Raphael : 콜라 객체 모델의 구현은 VTABLE 저장할 -1 위치 사용 piumarta.com/software/cola/objmodel2.pdf를 . 따라서 필드는 객체의 양수 부분에 저장되고 vtable은 음수에 저장됩니다. 나는 세부 사항을 기억할 수 없지만 일관성과 관련이 있다고 생각합니다.
Dave Clarke

@ DeZéroToxin : 배열은 실제로 메모리의 한 위치이며 그 옆에 일부는 논리적으로 배열되어 있습니다. 그러나 실제로 배열은 포인터 일뿐입니다.
Dave Clarke

1
@Raphael, a[-1]대한 완벽한 의미가 일부 의 경우 a불법 일반 (그러나 컴파일러에 의해 체포되지 않음) 인이 특정 경우에,
vonbrand

4

다른 답변에서 알 수 있듯이 이것은 C에서 정의 되지 않은 동작 입니다. C가 "고수준 어셈블러"로 정의 (및 대부분 사용됨)되는 것을 고려하십시오. C의 사용자는 타협하지 않는 속도로 평가하고 런타임에 물건을 검사하는 것은 (성능이) 성능을 높이기 위해 의문의 여지가 없습니다. 다른 언어에서 온 사람들에게 무의미 하게 보이는 일부 C 구문 은 C와 같이 완벽하게 이해됩니다 a[-1]. 예, 항상 의미 가있는 것은 아닙니다 (


1
나는이 답변을 좋아한다. 이것이 괜찮은 이유를 제시합니다.
darxsys

3

그러한 기능을 사용하여 메모리에 직접 액세스하는 메모리 할당 방법을 작성할 수 있습니다. 이러한 용도 중 하나는 음의 배열 인덱스를 사용하여 이전 메모리 블록을 확인하여 두 블록을 병합 할 수 있는지 확인하는 것입니다. 비 휘발성 메모리 관리자를 개발할 때이 기능을 사용했습니다.


2

C는 강력하게 입력되지 않습니다. 표준 C 컴파일러는 배열 범위를 확인하지 않습니다. 또 다른 것은 C의 배열은 연속적인 메모리 블록 일 뿐이며 인덱싱은 0에서 시작하므로 인덱스 -1은 이전의 비트 패턴 위치입니다 a[0].

다른 언어는 음수 지수를 좋은 방법으로 이용합니다. 파이썬에서는 a[-1]마지막 요소를 반환하고 마지막에서 마지막 요소 a[-2]를 반환합니다.


2
강력한 타이핑 및 배열 인덱스는 어떤 관련이 있습니까? 배열 인덱스가 자연어 여야하는 자연어 유형의 언어가 있습니까?
Raphael

@Raphael 내가 아는 한 강력한 타이핑은 유형 오류가 발생했음을 의미합니다. 배열은 유형이고 IndexOutOfBounds는 오류이므로 강력한 유형의 언어에서는 이것이보고되고 C에서는 그렇지 않습니다. 그게 내 뜻이야
saadtaame

내가 아는 언어에서 배열 인덱스는 유형 int이므로 a[-5]더 일반적으로 int i; ... a[i] = ...;올바르게 입력됩니다. 색인 오류는 런타임시에만 감지됩니다. 물론, 똑똑한 컴파일러는 감지 할 수 있습니다 약간의 위반.
Raphael

@Raphael 인덱스 타입이 아닌 배열 데이터 타입에 대해 이야기하고 있습니다. C가 사용자에게 a [-5]를 쓸 수있는 이유를 설명합니다. 예, -5는 올바른 인덱스 유형이지만 범위를 벗어 났으며 오류입니다. 내 대답에는 컴파일 또는 런타임 유형 검사에 대한 언급이 없습니다.
saadtaame

1

간단히 말해서 :

C의 모든 변수 (배열 포함)는 메모리에 저장됩니다. 14 바이트의 "메모리"가 있고 다음을 초기화한다고 가정 해 봅시다.

int a=0;
int array1[6] = {0, 1, 2, 3, 4, 5};

또한 int의 크기를 2 바이트로 고려하십시오. 가상 메모리의 처음 2 바이트에는 정수 a가 저장되고, 다음 2 바이트에는 배열의 첫 번째 위치의 정수가 저장됩니다 (즉, array [0]).

그렇다면, array [-1]은 array [0] 바로 앞에있는 메모리에 저장된 정수를 말하는 것과 같습니다. 실제로 이것은 변수가 메모리에 저장되는 방식이 아닙니다.


0
//:Example of negative index:
//:A memory pool with a heap and a stack:

unsigned char memory_pool[64] = {0};

unsigned char* stack = &( memory_pool[ 64 - 1] );
unsigned char* heap  = &( memory_pool[ 0     ] );

int stack_index =    0;
int  heap_index =    0;

//:reserve 4 bytes on stack:
stack_index += 4;

//:reserve 8 bytes on heap:
heap_index  += 8;

//:Read back all reserved memory from stack:
for( int i = 0; i < stack_index; i++ ){
    unsigned char c = stack[ 0 - i ];
    //:do something with c
};;
//:Read back all reserved memory from heap:
for( int i = 0; i < heap_index; i++ ){
    unsigned char c = heap[ 0 + i ];
    //:do something with c
};;

CS.SE에 오신 것을 환영합니다! 우리는 독서에 대한 설명이나 설명과 함께 답을 찾고 있습니다. 우리는 코딩 사이트가 아니며 단지 코드 블록 인 답변을 원하지 않습니다. 그런 종류의 정보를 제공하기 위해 답을 편집 할 수 있는지 고려할 수 있습니다 . 감사합니다!
DW
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.