왜 C와 C ++는 구조체 내에서 멤버 별 배열 할당을 지원하지만 일반적으로 지원하지 않습니까? {1,2,3}; int num2[3]; num2

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

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 의 참조도 유용했습니다.



답변

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

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 ++ 표준은 다음과 같이 말합니다 (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)보다 훨씬 간단합니다.

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

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


답변

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

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

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

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

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

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


답변

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

num2 = num1;

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

그리고 물론, struct2 = struct1;

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


답변

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

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