왜 C와 C ++는 구조체 내에서 멤버 별 배열 할당을 지원하지만 일반적으로 지원하지 않습니까?


87

멤버 별 배열 할당이 지원되지 않으므로 다음이 작동하지 않음을 이해합니다.

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"

나는 이것을 사실로 받아 들였고, 언어의 목적은 개방형 프레임 워크를 제공하는 것이고 사용자가 배열 복사와 같은 것을 구현하는 방법을 결정하게하는 것이라고 생각했습니다.

그러나 다음은 작동합니다.

struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;

배열 num[3]은의 인스턴스에서의 인스턴스로 멤버별로 할당 struct1됩니다 struct2.

구조체에 대해 멤버 별 배열 할당이 지원되지만 일반적으로 지원되지 않는 이유는 무엇입니까?

편집 : struct의 std :: string 스레드에서 Roger Pate 의 코멘트 -복사 / 할당 문제? 대답의 일반적인 방향을 가리키는 것 같지만 직접 확인할만큼 충분히 알지 못합니다.

편집 2 : 많은 훌륭한 응답. 나는 주로 행동의 철학적 또는 역사적 근거에 대해 궁금해했기 때문에 Luther Blissett을 선택 했지만 관련 사양 문서에 대한 James McNellis 의 참조도 유용했습니다.


6
이것은 C에서 비롯 되었기 때문에 C와 C ++를 모두 태그로 사용하고 있습니다. 또한 좋은 질문입니다.
— GManNickG 2010 년

4
오래 전에 C에서 구조 할당이 일반적으로 가능하지 않았고 사용 memcpy()하거나 유사 하게 사용해야한다는 점에 주목할 가치가 있습니다 .
— ggg

약간의 참고로 ... boost::array( boost.org/doc/libs/release/doc/html/array.html ) 그리고 지금 std::array( en.cppreference.com/w/cpp/container/array )은 STL 호환 대안입니다. 지저분한 오래된 C 배열. 복사 할당을 지원합니다.
— Emile Cormier 2013

@EmileCormier 그리고 그들은-타다! -배열 주변의 구조.
— Peter-Monica 복원

답변:


46

여기에 내 의견이 있습니다.

C 언어의 개발은 C 에서 배열 유형의 진화에 대한 통찰력을 제공합니다.

배열에 대해 간략히 설명하겠습니다.

C의 선구자 B와 BCPL에는 다음과 같은 선언과 같은 고유 한 배열 유형이 없습니다.

auto V[10] (B)
or 
let V = vec 10 (BCPL)

V는 메모리 10 "워드"의 사용되지 않는 영역을 가리 키도록 초기화되는 (유형이 지정되지 않은) 포인터로 선언됩니다. B는 이미 *포인터 역 참조에 사용 되었으며 오늘날 C / C ++에서 [] 와 *(V+i)같이을 의미 하는 약칭 표기법 V[i]을 사용했습니다. 그러나 V배열이 아니라 일부 메모리를 가리켜 야하는 포인터입니다. Dennis Ritchie가 구조체 유형으로 B를 확장하려고 할 때 문제가 발생했습니다. 그는 오늘날 C 에서처럼 배열이 구조체의 일부가되기를 원했습니다.

struct {
    int inumber;
    char name[14];
};

그러나 포인터로서 배열의 B, BCPL 개념을 사용하면 런타임 에 구조체 내 14 바이트의 메모리 영역에 대해 초기화 해야하는 name포인터를 필드에 포함해야했습니다 . 초기화 / 레이아웃 문제는 결국 배열에 특별한 처리를 제공함으로써 해결되었습니다. 컴파일러는 배열을 포함하는 표현식을 제외하고는 데이터에 대한 포인터를 실제로 요구하지 않고도 구조, 스택 등에서 배열의 위치를 ​​추적합니다. 이 처리를 통해 거의 모든 B 코드가 계속 실행될 수 있으며 "배열을 보면 포인터로 변환" 규칙 의 소스가 됩니다. 개방형 크기 등의 배열을 허용했기 때문에 매우 편리한 것으로 판명 된 호환성 해킹입니다.

그리고 배열을 할당 할 수없는 이유는 다음과 같습니다. 배열은 B의 포인터 였으므로 간단히 다음과 같이 작성할 수 있습니다.

auto V[10];
V=V+5;

"배열"을 리베이스합니다. 배열 변수의 기본이 더 이상 lvalue가 아니기 때문에 이것은 이제 의미가 없습니다. 따라서이 할당이 허용되지 않아 선언 된 배열을 기반으로 한 몇 가지 프로그램을 포착하는 데 도움이 되었습니다.. 그리고이 개념이 고착되었습니다. 배열은 C 유형 시스템에서 인용 된 일급 클래스로 설계되지 않았기 때문에 대부분 사용하면 포인터가되는 특수한 짐승으로 취급되었습니다. 그리고 특정 관점 (C- 어레이가 잘못된 해킹이라는 것을 무시 함)에서 배열 할당을 허용하지 않는 것은 여전히 ​​의미가 있습니다. 열린 배열 또는 배열 함수 매개 변수는 크기 정보가없는 포인터로 취급됩니다. 컴파일러에는 배열 할당을 생성 할 수있는 정보가 없으며 호환성을 위해 포인터 할당이 필요했습니다.

/* Example how array assignment void make things even weirder in C/C++, 
   if we don't want to break existing code.
   It's actually better to leave things as they are...
*/
typedef int vec[3];

void f(vec a, vec b) 
{
    vec x,y; 
    a=b; // pointer assignment
    x=y; // NEW! element-wise assignment
    a=x; // pointer assignment
    x=a; // NEW! element-wise assignment
}

1978 년 C의 개정판이 구조체 할당 ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf )을 추가했을 때 변경되지 않았습니다 . 레코드 는 C에서 고유 한 유형 이었지만 초기 K & R C에서는이를 할당 할 수 없었습니다. memcpy를 사용하여 멤버별로 복사해야했고 함수 매개 변수로 포인터 만 전달할 수있었습니다. 어시 그먼트 (및 매개 변수 전달)는 이제 단순히 구조체 원시 메모리의 memcpy로 정의되었으며 기존 코드를 깰 수 없었기 때문에 쉽게 배치되었습니다. 의도하지 않은 부작용으로 이것은 암시 적으로 일종의 배열 할당을 도입했지만 구조 내부 어딘가에서 발생했기 때문에 배열이 사용되는 방식에 실제로 문제를 일으킬 수 없었습니다.


C가 구문을 정의하지 않았다는 것이 너무 나쁩니다. 예 int[10] c;를 들어 lvalue c가 10 개 항목 배열의 첫 번째 항목에 대한 포인터가 아닌 10 개 항목의 배열처럼 동작 하도록 만드는 것입니다. 변수에 사용할 때 공간을 할당하지만 함수 인수로 사용할 때는 포인터를 전달하는 typedef를 만들 수있는 것이 유용한 몇 가지 상황이 있지만 배열 유형의 값을 가질 수 없다는 것은 의미 론적 약점입니다. 언어로.
— supercat

"일부 메모리를 가리켜 야하는 포인터"라고 말하는 대신, 중요한 점은 포인터 자체가 일반 포인터처럼 메모리에 저장 되어야한다는 것 입니다. 이것은 이후의 설명에서 발견되지만 주요 차이점을 더 잘 강조한다고 생각합니다. (현대 C에서 배열 변수의 이름은 메모리 블록을 참조하므로 차이가 없습니다. 포인터 자체가 추상 기계의 어디에도 논리적으로 저장되지 않는다는 것입니다.)
— Peter Cordes

역사에 대한 멋진 요약은 배열에 대한 C의 혐오감을 참조하십시오 .
— Peter Cordes

31

할당 연산자와 관련하여 C ++ 표준은 다음과 같이 말합니다 (C ++ 03 §5.17 / 1).

여러 할당 연산자가 있습니다. 모두 왼쪽 피연산자로 수정 가능한 lvalue가 필요합니다.

배열은 수정 가능한 lvalue가 아닙니다.

그러나 클래스 유형 개체에 대한 할당은 특별히 정의됩니다 (§5.17 / 4).

클래스의 개체에 대한 할당은 복사 할당 연산자에 의해 정의됩니다.

따라서 클래스에 대해 암시 적으로 선언 된 복사 할당 연산자가 수행하는 작업을 살펴 ​​봅니다 (§12.8 / 13).

클래스 X에 대해 암시 적으로 정의 된 복사 할당 연산자는 하위 객체의 멤버 별 할당을 수행합니다. ... 각 하위 개체는 해당 유형에 적합한 방식으로 할당됩니다.
...-
하위 개체가 배열 인 경우 각 요소가 요소 유형에 적합한 방식으로 할당됩니다
.

따라서 클래스 유형 객체의 경우 배열이 올바르게 복사됩니다. 사용자 선언 복사 할당 연산자를 제공하면이를 활용할 수 없으며 배열을 요소별로 복사해야합니다.


이유는 C (C99 §6.5.16 / 2)에서 유사합니다.

할당 연산자는 왼쪽 피연산자로 수정 가능한 lvalue를 가져야합니다.

그리고 §6.3.2.1 / 1 :

수정 가능한 lvalue는 배열 유형이없는 lvalue입니다 ... [다른 제약 조건이 따릅니다]

C에서 할당은 C ++ (§6.5.16.1 / 2)보다 훨씬 간단합니다.

단순 대입 (=)에서는 오른쪽 피연산자의 값이 대입 식의 유형으로 변환되고 왼쪽 피연산자가 지정한 오브젝트에 저장된 값을 대체합니다.

구조체 유형 객체를 할당하려면 왼쪽 및 오른쪽 피연산자의 유형이 동일해야하므로 오른쪽 피연산자의 값이 왼쪽 피연산자로 간단히 복사됩니다.


1
배열이 변경 불가능한 이유는 무엇입니까? 아니면 클래스 유형에있을 때와 같이 배열에 대해 할당이 특별히 정의되지 않는 이유는 무엇입니까?
— GManNickG 2010 년

1
@GMan : 그게 더 흥미로운 질문 이군요. C ++의 경우 대답은 아마도 "C에있는 방식이기 때문일 것입니다."이고 C의 경우 언어가 진화 한 방식 때문이라고 생각합니다 (즉, 그 이유는 기술적이지 않은 역사적 이유).하지만 나는 살아 있지 않았습니다. 대부분의 경우 그 부분에 대한 답을 좀 더 알고있는 사람에게 맡기겠습니다. -P (FWIW, C90 또는 C99 근거 문서에서 아무것도 찾을 수 없습니다).
— James McNellis 2010 년

2
C ++ 03 표준에서 "수정 가능한 lvalue"의 정의가 어디에 있는지 아는 사람이 있습니까? 그것은 해야 §3.10에합니다. 색인은 해당 페이지에 정의되어 있다고 말하지만 그렇지 않습니다. §8.3.4 / 5의 (비 규범 적) 메모에는 "배열 유형의 개체는 수정할 수 없습니다. 3.10 참조"라고 나와 있지만 §3.10에서는 "배열"이라는 단어를 한 번 사용하지 않습니다.
— James McNellis 2010 년

@James : 저도 똑같이하고있었습니다. 제거 된 정의를 참조하는 것 같습니다. 그리고 예, 나는 항상 그 모든 것의 진짜 이유를 알고 싶었지만 그것은 수수께끼처럼 보입니다. "실수로 배열을 할당하여 사람들이 비효율적이되는 것을 방지"와 같은 말을 들었지만 말도 안됩니다.
— GManNickG 2010 년

1
@GMan, James : 최근 comp.lang.c ++ groups.google.com/group/comp.lang.c++/browse_frm/thread/… 에 대한 토론 이있었습니다. 배열이 아닌 수정 좌변 때문에 분명히 그렇지 않은 (배열 확실히 좌변 모든 const가 아닌 lvalues가 수정할 수 있습니다이다) 때문이 아니라 =요구 를 rvalue 온 RHS을 하고 배열이 될 수 없습니다 를 rvalue ! lvalue-to-rvalue 변환은 배열에 대해 금지되며 lvalue-to-pointer로 대체됩니다. static_cast동일한 용어로 정의되기 때문에 rvalue를 만드는 데 더 좋지 않습니다.
— Potatoswatter

2

이 링크 : http://www2.research.att.com/~bs/bs_faq2.html 배열 할당에 대한 섹션이 있습니다.

배열의 두 가지 근본적인 문제는

  • 배열은 자신의 크기를 모릅니다
  • 배열의 이름은 약간의 도발에서 첫 번째 요소에 대한 포인터로 변환됩니다.

그리고 이것이 배열과 구조체의 근본적인 차이점이라고 생각합니다. 배열 변수는 자기 지식이 제한된 저수준 데이터 요소입니다. 기본적으로 메모리 덩어리와 색인화 방법입니다.

따라서 컴파일러는 int a [10]과 int b [20]의 차이를 알 수 없습니다.

그러나 구조체에는 동일한 모호성이 없습니다.


3
이 페이지는 배열을 함수에 전달하는 방법에 대해 설명합니다 (이는 수행 할 수 없으므로 포인터 일 뿐이며 크기를 잃었다 고 말할 때 의미합니다). 그것은 배열에 배열을 할당하는 것과 관련이 없습니다. 그리고 아니요, 배열 변수는 첫 번째 요소에 대한 "정말"포인터가 아니라 배열입니다. 배열은 포인터가 아닙니다.
— GManNickG 2010 년

의견을 보내 주셔서 감사합니다. 그러나 기사의 해당 섹션을 읽었을 때 그는 배열이 자체 크기를 알지 못한다고 말한 다음 그 사실을 설명하기 위해 배열이 인수로 전달되는 예제를 사용합니다. 따라서 배열이 인수로 전달 될 때 크기에 대한 정보를 잃었거나 시작할 정보가 없었습니까? 나는 후자를 가정했다.
— Scott Turley

3
컴파일러는 두 개의 서로 다른 크기의 배열의 차이를 말할 수있다 - 인쇄 해보십시오 sizeof(a)대를 sizeof(b)하거나 통과 a에 void f(int (&)[20]);.
— Georg Fritzsche

각 배열 크기가 고유 한 유형을 구성한다는 것을 이해하는 것이 중요합니다. 매개 변수 전달 규칙은 크기를 개별적으로 전달해야하는 대신 모든 크기의 배열 인수를 사용하는 가난한 사람의 "일반"함수를 작성할 수 있도록합니다. 그렇지 않은 경우 (그리고 C ++에서 특정 크기의 배열에 대한 참조 매개 변수를 정의 할 수 있으며 반드시 정의해야합니다), 분명히 말도 안되는 각기 다른 크기에 대한 특정 함수가 필요합니다. 나는 그것에 대해 다른 포스트 에 썼다 .
— Peter-Monica 복원

0

대답 한 모든 사람은 C / C ++ 전문가입니다. 하지만 이것이 주된 이유라고 생각했습니다.

num2 = num1;

여기서는 허용되지 않는 배열의 기본 주소를 변경하려고합니다.

그리고 물론, struct2 = struct1;

여기서 객체 struct1은 다른 객체에 할당됩니다.


그리고 구조체를 할당하면 결국 동일한 질문을 던지는 배열 구성원이 할당됩니다. 두 상황 모두에서 배열 인 경우 하나는 허용되고 다른 하나는 허용되지 않는 이유는 무엇입니까?
— GManNickG 2010 년

1
동의합니다. 그러나 첫 번째는 컴파일러에 의해 방지됩니다 (num2 = num1). 두 번째는 컴파일러에 의해 방지되지 않습니다. 그것은 큰 차이를 만듭니다.
— nsivakr 2010 년

배열을 할당 num2 = num1할 수 있다면 완벽하게 잘 작동합니다. 의 요소는 num2의 해당 요소와 동일한 값을 갖습니다 num1.
— juanchopanza

0

더 이상의 노력이 C에서 배열을 강화하기 위해 만들어진 또 다른 이유는 배열 할당하지 않다고 아마 그 유용합니다. 구조체로 래핑하여 C에서 쉽게 얻을 수 있지만 (구조체의 주소는 추가 처리를 위해 단순히 배열의 주소 또는 배열의 첫 번째 요소 주소로 캐스팅 할 수 있음)이 기능은 거의 사용되지 않습니다. 한 가지 이유는 서로 다른 크기의 배열이 호환되지 않아 할당의 이점을 제한하거나 값에 따라 함수에 전달하기 때문입니다.

배열이 일급 유형 인 언어에서 배열 매개 변수가있는 대부분의 함수는 임의 크기의 배열에 대해 작성됩니다. 그런 다음 함수는 일반적으로 배열이 제공하는 정보 인 지정된 수의 요소를 반복합니다. (C에서 관용구는 물론 포인터와 별도의 요소 수를 전달하는 것입니다.) 하나의 특정 크기의 배열을 받아들이는 함수는 자주 필요하지 않으므로 많이 놓치지 않습니다. (이것은 C ++ 템플릿 에서처럼 발생하는 배열 크기에 대해 별도의 함수를 생성하기 위해 컴파일러에 맡길 수있을 때 변경됩니다. 이것이 std::array유용한 이유 입니다.)

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