C 및 C ++ 컴파일러가 강제로 실행되지 않을 때 함수 시그니처에서 배열 길이를 허용하는 이유는 무엇입니까? 학습 기간 동안 찾은

이것이 제가 학습 기간 동안 찾은 것입니다 :

#include<iostream>
using namespace std;
int dis(char a[1])
{
    int length = strlen(a);
    char c = a[2];
    return length;
}
int main()
{
    char b[4] = "abc";
    int c = dis(b);
    cout << c;
    return 0;
}  

따라서 변수 int dis(char a[1])에서는
사용할 수 있기 때문에 [1]아무것도하지 않는 것으로 보이며 전혀 작동하지 않습니다 . 또는 처럼 . 배열 이름이 포인터이고 배열을 전달하는 방법을 알고 있으므로 내 퍼즐은이 부분에 관한 것이 아닙니다.
a[2]int a[]char *a

내가 알고 싶은 것은 컴파일러 가이 동작 ( int a[1])을 허용하는 이유 입니다. 아니면 내가 모르는 다른 의미가 있습니까?



답변

배열을 함수에 전달하기위한 구문입니다.

실제로 C에서는 배열을 전달할 수 없습니다. 배열을 전달해야하는 것처럼 보이는 구문을 작성하면 실제로 배열의 첫 번째 요소에 대한 포인터가 대신 전달됩니다.

포인터는 길이 정보를 포함하지 않으므로 []함수 형식 매개 변수 목록에있는 내용 은 실제로 무시됩니다.

이 구문을 허용하기로 한 결정은 1970 년대에 이루어졌으며 그 이후로 많은 혼란을 야기했습니다.


답변

첫 번째 차원의 길이는 무시되지만 컴파일러가 오프셋을 올바르게 계산하려면 추가 차원의 길이가 필요합니다. 다음 예제에서 foo함수에는 2 차원 배열에 대한 포인터가 전달됩니다.

#include <stdio.h>

void foo(int args[10][20])
{
    printf("%zd\n", sizeof(args[0]));
}

int main(int argc, char **argv)
{
    int a[2][20];
    foo(a);
    return 0;
}

첫 번째 차원의 크기 [10]는 무시됩니다. 컴파일러는 끝에서 색인을 생성하지 못하게하지 않습니다 (공식은 10 요소를 원하지만 실제는 2 개만 제공한다는 점에 유의하십시오) 그러나 두 번째 차원의 크기는 [20]각 행의 보폭을 결정하는 데 사용되며 여기서 공식은 실제와 일치해야합니다. 다시 말하지만 컴파일러는 두 번째 차원의 끝에서 색인을 생성하지 못하게하지 않습니다.

배열의베이스에서 요소 args[row][col]로 의 바이트 오프셋 은 다음에 의해 결정됩니다.

sizeof(int)*(col + 20*row)

인 경우 col >= 20실제로 다음 행으로 색인을 생성합니다 (또는 전체 배열의 끝에서 벗어남).

sizeof(args[0]), 80내 컴퓨터 에서을 ( 를 ) 반환합니다 sizeof(int) == 4. 그러나을 시도 sizeof(args)하면 다음과 같은 컴파일러 경고가 표시됩니다.

foo.c:5:27: warning: sizeof on array function parameter will return size of 'int (*)[20]' instead of 'int [10][20]' [-Wsizeof-array-argument]
    printf("%zd\n", sizeof(args));
                          ^
foo.c:3:14: note: declared here
void foo(int args[10][20])
             ^
1 warning generated.

여기서 컴파일러는 배열 자체의 크기 대신 배열이 붕괴 된 포인터의 크기 만 제공 할 것이라고 경고합니다.


답변

C ++에서 문제와이를 극복하는 방법

이 문제는 patMatt에 의해 광범위하게 설명되었습니다 . 컴파일러는 기본적으로 전달 된 인수의 크기를 무시하고 배열 크기의 첫 번째 차원을 무시합니다.

반면 C ++에서는 다음 두 가지 방법으로이 제한을 쉽게 극복 할 수 있습니다.

  • 참조 사용
  • 사용하기 std::array(C ++ 11부터)

참고 문헌

함수가 기존 배열을 읽거나 수정하려고 시도하는 경우 (복사 아님) 참조를 쉽게 사용할 수 있습니다.

예를 들어, int모든 요소를로 설정 하는 10 의 배열을 재설정하는 함수를 원한다고 가정 해 봅시다 0. 다음 함수 서명을 사용하면 쉽게 수행 할 수 있습니다.

void reset(int (&array)[10]) { ... }

뿐만 아니라이됩니다 잘 작동 하지만, 그것은 또한 것이다 배열의 차원을 적용 .

템플릿 을 사용하여 위의 코드를 일반적 으로 만들 수도 있습니다 .

template<class Type, std::size_t N>
void reset(Type (&array)[N]) { ... }

마지막으로 const정확성 을 활용할 수 있습니다 . 10 개의 요소 배열을 인쇄하는 함수를 생각해 봅시다.

void show(const int (&array)[10]) { ... }

const한정자 를 적용하여 가능한 수정을 방지합니다 .


배열의 표준 라이브러리 클래스

위와 같은 구문을 추악하고 불필요하게 생각한다면, 캔에 던져서 std::array대신 사용할 수 있습니다 (C ++ 11부터).

리팩토링 된 코드는 다음과 같습니다.

void reset(std::array<int, 10>& array) { ... }
void show(std::array<int, 10> const& array) { ... }

멋지지 않습니까? 앞서 가르친 일반적인 코드 트릭 은 말할 것도없이 여전히 작동합니다.

template<class Type, std::size_t N>
void reset(std::array<Type, N>& array) { ... }

template<class Type, std::size_t N>
void show(const std::array<Type, N>& array) { ... }

뿐만 아니라 복사 및 시맨틱을 무료로 얻을 수 있습니다. 🙂

void copy(std::array<Type, N> array) {
    // a copy of the original passed array 
    // is made and can be dealt with indipendently
    // from the original
}

그래서, 당신은 무엇을 기다리고 있습니까? 사용하십시오 std::array.


답변

C 의 재미있는 기능으로 기울어 진 상태에서 발을 효과적으로 쏠 수 있습니다.

그 이유는 C 가 어셈블리 언어보다 한 단계 높은 단계 라고 생각합니다 . 최대 성능을 위해 크기 검사유사한 안전 기능이 제거되었으므로 프로그래머가 부지런한 경우에는 나쁘지 않습니다.

또한 함수 인수에 크기 를 할당하면 다른 프로그래머가 함수를 사용할 때 크기 제한이 나타날 가능성이 있습니다. 포인터를 사용 한다고해서 그 정보가 다음 프로그래머에게 전달되지는 않습니다.


답변

첫째, C는 배열 경계를 확인하지 않습니다. 로컬, 전역, 정적, 매개 변수 등은 중요하지 않습니다. 배열 경계 검사는 더 많은 처리를 의미하며 C는 매우 효율적이므로 배열 경계 검사는 필요할 때 프로그래머가 수행합니다.

둘째, 배열에 값을 전달하여 함수에 전달할 수있는 트릭이 있습니다. 함수에서 값으로 배열을 반환 할 수도 있습니다. struct를 사용하여 새 데이터 유형을 작성하면됩니다. 예를 들면 다음과 같습니다.

typedef struct {
  int a[10];
} myarray_t;

myarray_t my_function(myarray_t foo) {

  myarray_t bar;

  ...

  return bar;

}

foo.a [1]과 같은 요소에 액세스해야합니다. 여분의 “.a”는 이상하게 보일 수 있지만이 방법은 C 언어에 뛰어난 기능을 추가합니다.


답변

컴파일러에게 myArray가 10 개 이상의 정수 배열을 가리 키도록하려면 :

void bar(int myArray[static 10])

myArray [10]에 액세스하면 좋은 컴파일러가 경고를 표시해야합니다. “static”키워드가 없으면 10은 전혀 의미가 없습니다.


답변

C ++은 C 코드를 올바르게 컴파일해야하기 때문에 C의 잘 알려진 “기능”입니다.

여러 측면에서 문제가 발생합니다.

  1. 배열 이름은 포인터와 완전히 동일해야합니다.
  2. C는 빠르며 원래는 “고수준 어셈블러”(특히 첫 번째 “휴대용 운영 체제”: Unix를 작성하도록 설계됨)로 개발되었으므로 “숨겨진”코드를 삽입 하지 않아야합니다. 따라서 런타임 범위 검사는 “금지”됩니다.
  3. 정적 배열 또는 동적 배열 (스택 또는 할당)에 액세스하도록 생성 된 머신 코드는 실제로 다릅니다.
  4. 호출 된 함수는 인수로 전달 된 배열의 “종류”를 알 수 없으므로 모든 것이 포인터로 간주되고 그렇게 취급됩니다.

배열이 C에서 실제로 지원되지 않는다고 말할 수 있습니다 (이전에 말한 것처럼 사실은 아니지만 좋은 근사치입니다). 배열은 실제로 데이터 블록에 대한 포인터로 취급되며 포인터 산술을 사용하여 액세스됩니다. C에는 RTTI 형식이 없으므로 함수 프로토 타입에서 배열 요소의 크기를 선언해야합니다 (포인터 산술 지원). 이것은 다차원 배열에서도 “더 사실적”입니다.

어쨌든 위의 모든 것이 더 이상 사실이 아닙니다 : p

대부분의 최신 C / C ++ 컴파일러 범위 검사를 지원하지만 표준에서는 기본적으로 (이전 버전과의 호환성을 위해) 꺼져 있어야합니다. 예를 들어 합리적으로 최신 버전의 gcc는 “-O3 -Wall -Wextra”를 사용하여 컴파일 타임 범위를 확인하고 “-fbounds-checking”을 사용하여 전체 런타임 범위를 확인합니다.