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


131

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

#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])을 허용하는 이유 입니다. 아니면 내가 모르는 다른 의미가 있습니까?


6
실제로 배열을 함수에 전달할 수 없기 때문입니다.
Ed S.

37
여기서 질문은 포인터가 어쨌든 포인터처럼 정확하게 작동 할 때 C가 매개 변수를 배열 유형으로 선언 할 수있게 해주는 이유라고 생각합니다.
Brian

8
@Brian : 이것이 동작에 대한 인수인지 또는 반대인지 확실하지 않지만 인수 유형이 typedef배열 유형 인 경우에도 적용됩니다 . 따라서 인수 유형의 "포인터에 대한 붕괴"는 구문 설탕으로 대체 []되는 것이 아니라 *실제로 유형 시스템을 통과합니다. 이는 va_list배열 또는 비 배열 유형으로 정의 될 수있는 일부 표준 유형에 실제 결과를 초래 합니다.
R .. GitHub 중지 지원 얼음

4
@songyuanyao 포인터를 사용하여 C (및 C ++)와 완전히 다른 점을 달성 할 수 있습니다 int dis(char (*a)[1]). 그런 다음 배열에 대한 포인터를 전달합니다 dis(&b). C ++에 존재하지 않는 C 기능을 기꺼이 사용하려는 경우 void foo(int data[static 256])and와 같은 것을 말할 수는 int bar(double matrix[*][*])있지만 다른 웜 수 있습니다.
스튜어트 올슨

1
@StuartOlsen 요점은 어떤 표준이 무엇을 정의했는지가 아닙니다. 요점은 그것을 정의한 사람 이 그렇게 정의한 이유 입니다.
user253751

답변:


156

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

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

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

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


21
C가 아닌 프로그래머로서 나는이 답변이 매우 접근하기 쉽다고 생각한다. +1
asteri

21
+1 "이 구문을 허용하는 결정은 1970 년대에 내려졌으며 그 이후로 많은 혼란을 야기했습니다 ..."
NoSenseEtAl

8
이것은 사실이지만 구문을 사용하여 해당 크기 의 배열을 전달할 수도 있습니다 void foo(int (*somearray)[20]). 이 경우 발신자 사이트에 20이 적용됩니다.
v.oddou

14
-1 C 프로그래머로서이 답변이 잘못되었습니다. []pat의 답변에 표시된 것처럼 다차원 배열에서는 무시되지 않습니다. 따라서 배열 구문을 포함해야했습니다. 또한, 1 차원 배열에서도 컴파일러가 경고를 발행하는 것을 막을 수있는 것은 없습니다.
user694733

7
"[]의 내용"을 통해 질문의 코드에 대해 구체적으로 이야기하고 있습니다. 이 구문 문제는 전혀 필요하지 않았습니다. 포인터 구문을 사용하여 동일한 것을 수행 할 수 있습니다. 즉, 포인터가 전달되면 매개 변수가 포인터 선언자가되도록 요구합니다. 예를 들어 pat의 예에서 void foo(int (*args)[20]);, 엄격하게 말하면 C에는 다차원 배열이 없습니다. 그러나 요소가 다른 배열 일 수있는 배열이 있습니다. 이것은 아무것도 바뀌지 않습니다.
MM

143

첫 번째 차원의 길이는 무시되지만 컴파일러가 오프셋을 올바르게 계산하려면 추가 차원의 길이가 필요합니다. 다음 예제에서 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.

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


매우 유용합니다-이것과의 일관성은 1-d 경우의 기발한 이유로도 그럴듯합니다.
jwg

1
1D 사례와 같은 생각입니다. C 및 C ++에서 2 차원 배열은 실제로 1 차원 배열이며 각 요소는 다른 1 차원 배열입니다. 이 경우 10 개의 요소가있는 배열이 있으며 각 요소는 "20 개의 배열"입니다. 내 게시물에서 설명한 것처럼 실제로 함수에 전달되는 것은의 첫 번째 요소에 대한 포인터입니다 args. 이 경우 args의 첫 번째 요소는 "20 int의 배열"입니다. 포인터는 타입 정보를 포함합니다. 전달되는 것은 "20 int의 배열에 대한 포인터"입니다.
MM

9
그렇습니다, 그것이 int (*)[20]유형입니다. "20 정수 배열의 포인터"
pat

33

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.


2
@ kietz, 제안 된 편집이 거부되어 죄송하지만 달리 지정하지 않는 한 C ++ 11이 자동으로 사용 됩니다.
구두

이것은 사실이지만 우리가 제공 한 링크를 기반으로 솔루션이 C ++ 11 인 경우도 지정해야합니다.
trlkly

@trlkly, 동의합니다. 그에 따라 답변을 편집했습니다. 지적 해 주셔서 감사합니다.
구두

9

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

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

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


3
예. C는 컴파일러보다 프로그래머를 신뢰하도록 설계되었습니다. 배열의 끝을 너무 뻔뻔하게 색인하는 경우, 특별하고 의도적 인 일을해야합니다.
John

7
나는 14 년 전 C 프로그래밍에서 이빨을 잘라 냈다. 교수님은 "C는 프로그래머를 위해 프로그래머에 의해 작성되었습니다." 언어는 매우 강력합니다. 벤 삼촌이 우리에게 가르쳤을 때 "강한 힘으로 큰 책임이 따른다."
Andrew Falanga

6

첫째, 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 언어에 뛰어난 기능을 추가합니다.


7
런타임 경계 검사와 컴파일 타임 유형 검사를 혼동하고 있습니다.
벤 Voigt

@ Ben Voigt : 원래 질문과 마찬가지로 경계 검사에 대해서만 이야기하고 있습니다.
user34814

2
@ user34814 컴파일 타임 바운드 검사는 유형 검사 범위 내에 있습니다. 여러 고급 언어가이 기능을 제공합니다.
Leushenko

5

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

void bar(int myArray[static 10])

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


1
11 번째 요소에 액세스하고 배열에 10 개 이상의 요소 가 포함 된 경우 컴파일러에서 경고해야하는 이유는 무엇 입니까?
nwellnhof

아마도 이것은 컴파일러가 최소한 10 개의 요소 를 갖도록 강제 할 수 있기 때문 입니다. 11 번째 요소에 액세스하려고하면 해당 요소 가 존재하더라도 확실 하지 않습니다 .
Dylan Watson

2
나는 그것이 표준을 올바르게 읽은 것이라고 생각하지 않습니다. [static]당신이 경우 경고 컴파일러 수 있습니다 전화 bar 로를 int[5]. 그것은 당신이 액세스 할 수있는 무엇을 지시하지 않습니다 bar . 종양은 전적으로 발신자 측에 있습니다.
tab

3
error: expected primary-expression before 'static'이 구문을 본 적이 없습니다. 이것은 표준 C 또는 C ++가 아닐 것입니다.
v.oddou

3
@ v.oddou, C7.5, 6.7.5.2 및 6.7.5.3에 지정되어 있습니다.
Samuel Edwin Ward

5

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

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

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

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

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

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


아마 C ++가 되었다 전에 20 년 C 코드를 컴파일 할 생각하지만, 그것은 확실히 이다 하지, 그리고 (모든 새로운 C ++ 표준에 "고정"되지 않은 적어도 C ++ 98? C99) 오랫동안 않았습니다.
hyde

@hyde 저에게 너무 가혹하게 들립니다. Stroustrup의 말을 인용하자면 "C는 C ++의 부분 집합입니다." (C ++ PL 4th ed., sec. 1.2.1). C ++과 C가 더 발전하고 최신 C ++ 버전에는없는 최신 C 버전의 기능이 존재하지만 전반적으로 Stroustrup 따옴표는 여전히 유효하다고 생각합니다.
mvw

@mvw이 밀레니엄으로 작성된 대부분의 C 코드는 호환되지 않는 기능을 피함으로써 의도적으로 C ++와 호환되지는 않지만 구조체를 초기화 하는 데 C99로 지정된 초기화 구문 ( struct MyStruct s = { .field1 = 1, .field2 = 2 };)을 사용합니다 . 구조체를 초기화하는 훨씬 명확한 방법이기 때문입니다. 결과적으로 대부분의 C 코드는 구조체를 초기화하므로 표준 C ++ 컴파일러는 최신 C 코드를 거부합니다.
hyde

@mvw 아마도 C ++은 C와 호환되어야한다고 말할 수 있습니다. 그래서 특정 절충이 이루어지면 C와 C ++ 컴파일러로 컴파일 할 코드를 작성할 수 있습니다. 하지만 그 중 일부 사용해야합니다 모두 ++, 단지 C ++의 부분 집합하지 C와 C를.
hyde

@hyde C 코드가 얼마나 C ++ 컴파일 가능한지 놀랄 것이다. 몇 년 전 전체 Linux 커널은 C ++로 컴파일 할 수있었습니다 (여전히 사실인지는 모르겠습니다). 나는 우수한 경고 검사를 얻기 위해 C ++ 컴파일러에서 C 코드를 일상적으로 컴파일한다. "생산"만이 C 모드에서 컴파일되어 가장 최적화되었다.
ZioByte

3

C 형은 파라미터 변환되지 int[5]로를 *int; 선언 주어 typedef int intArray5[5];,이 유형의 매개 변수를 변화시킬 intArray5으로 *int뿐만 아니라. 이 동작이 홀수이지만 유용 할 수있는 상황이 있습니다 (특히 , 구현은 배열로 va_list정의 된에 stdargs.h정의 된 것과 같은 경우). int[5](치수 무시) 로 정의 된 유형을 매개 변수로 허용하지만 int[5]직접 지정할 수 는 없는 것은 비논리적 입니다.

배열 유형의 매개 변수에 대한 C의 처리가 터무니없는 것으로 나타 났지만, 특히 잘 정의되지 않았거나 생각하지 않은 임시 언어를 사용하여 행동을 취하려고 시도한 결과입니다. 기존 구현이 기존 프로그램에 대해 수행 한 것과 일치하는 사양 C의 많은 단점은 그 관점에서 볼 때 특히 의미가 있습니다. 특히 많은 사람들이 발명되었을 때 오늘날 우리가 알고있는 언어의 상당 부분은 아직 존재하지 않습니다. 필자가 이해 한 바에 따르면 BCPL이라고하는 C에서 컴파일러는 변수 유형을 잘 추적하지 못했습니다. 선언 int arr[5];int anonymousAllocation[5],*arr = anonymousAllocation;; 일단 할당이 제외되면. 컴파일러는 알지도 신경 쓰지 않았는지arr포인터 또는 배열이었습니다. arr[x]또는 로 액세스하면 *arr선언 방법에 관계없이 포인터로 간주됩니다.


1

아직 답변되지 않은 한 가지는 실제 질문입니다.

이미 주어진 답변은 배열을 C 또는 C ++의 함수에 값으로 전달할 수 없다는 것을 설명합니다. 또한로 선언 된 매개 변수는 int[]마치 type을 가진 것처럼 취급되며 type int *의 변수 int[]가 그러한 함수에 전달 될 수 있다고 설명합니다.

그러나 배열 길이를 명시 적으로 제공하는 데 왜 오류가 발생하지 않았는지 설명하지 않습니다.

void f(int *); // makes perfect sense
void f(int []); // sort of makes sense
void f(int [10]); // makes no sense

마지막 오류가 왜 오류가 아닌가?

그 이유는 typedef에 문제가 있기 때문입니다.

typedef int myarray[10];
void f(myarray array);

함수 매개 변수에서 배열 길이를 지정하는 것이 오류 인 경우, 함수 매개 변수에서 myarray이름 을 사용할 수 없습니다 . 그리고 일부 구현은와 같은 표준 라이브러리 유형에 대해 배열 유형을 사용 va_list하고 모든 구현은 jmp_buf배열 유형 을 만드는 데 필요하므로 해당 이름을 사용하여 함수 매개 변수를 선언하는 표준 방법이 없다면 매우 문제가 될 수 있습니다. 와 같은 기능의 이식 가능한 구현이 아닙니다 vprintf.


0

컴파일러는 전달 된 배열의 크기가 예상 한 것과 동일한 지 여부를 확인할 수 있습니다. 그렇지 않은 경우 컴파일러가 문제를 경고 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.