C에서 어레이를 반환할 수 없다는 것은 실제로 무엇을 의미합니까?
저는 C가 어레이를 반환할 수 없다는 일반적인 질문을 재현하려는 것이 아니라 좀 더 깊이 파고들려고 합니다.
이렇게는 할 수 없습니다.
char f(void)[8] {
char ret;
// ...fill...
return ret;
}
int main(int argc, char ** argv) {
char obj_a[10];
obj_a = f();
}
하지만 우리는 할 수 있다:
struct s { char arr[10]; };
struct s f(void) {
struct s ret;
// ...fill...
return ret;
}
int main(int argc, char ** argv) {
struct s obj_a;
obj_a = f();
}
그래서 gcc - S에 의해 생성된 ASM 코드를 스킵하고 있었는데 스택으로 어드레싱을 하고 있는 것 같습니다.-x(%rbp)
다른 C 함수 반환과 마찬가지로.
어레이를 직접 반환하는 것은 무엇입니까?즉, 최적화나 계산의 복잡성이 아니라 구조층 없이 실제로 실행할 수 있는 능력의 관점에서 말입니다.
추가 데이터:x64 인텔에서 Linux와 gcc를 사용하고 있습니다.
우선 어레이를 구조체에 캡슐화한 후 해당 구조체에 대해 원하는 작업을 수행할 수 있습니다(할당, 함수에서 반환 등).
두 번째로 컴파일러는 이미 알고 있듯이 구조를 반환(또는 할당)하기 위한 코드를 내보내는 데 거의 어려움이 없습니다.그렇기 때문에 어레이를 반환할 수 없는 것도 아닙니다.
이렇게 할 수 없는 근본적인 이유는 어레이가 C의 2등급 데이터 구조이기 때문입니다.다른 모든 데이터 구조는 1등급입니다.이런 의미에서 '일등'과 '이등'의 정의는 무엇입니까?단순히 세컨드 클래스 유형을 할당할 수 없습니다.
(다음 질문은 "어레이 외에 다른 2종류의 데이터 타입은 없습니까?", "함수를 세지 않는 한 별로 없습니다."라고 대답할 수 있습니다.)
어레이를 반환(또는 할당할 수 없음)할 수 없는 것과 밀접하게 관련되어 있는 것은 어레이 타입의 값도 없다는 것입니다.배열 유형의 개체(변수)가 있지만 값을 가져오려고 할 때마다 배열의 첫 번째 요소에 대한 포인터가 즉시 표시됩니다.[주: 어레이 타입의 오브젝트는 할당 불가능한 값이지만 어레이 타입의 rvalue는 없습니다.]
따라서 어레이에 할당할 수 없다는 사실은 차치하고 할당하려는 값조차 생성할 수 없습니다.만약 당신이 말한다면
char a[10], b[10];
a = b;
마치 당신이 쓴 것처럼
a = &b[0];
왼쪽에는 어레이가 있고 오른쪽에는 포인터가 있습니다.어레이를 할당할 수 있어도 큰 미스매치가 발생합니다.마찬가지로 (당신의 예에서) 다음과 같이 쓰려고 하면
a = f();
그리고 기능의 정의 안 어딘가에 있다.f()
우리는 가지고 있다.
char ret[10];
/* ... fill ... */
return ret;
마치 그 마지막 행이 말하는 것처럼
return &ret[0];
다시 말씀드리지만 어레이 값을 반환하고 할당할 필요가 없습니다.a
, 단순한 포인터입니다.
(이 함수 호출 예에서는 다음과 같은 매우 중요한 문제도 발생하고 있습니다.ret
로컬 어레이이므로 C로 되돌리려고 하면 위험합니다.이 점에 대해서는 나중에 자세히 설명하겠습니다.)
질문의 일부는 아마도 "왜 이런 방식입니까?"와 "어레이를 할당할 수 없는 경우 어레이를 포함하는 구조를 할당할 수 있는 이유는 무엇입니까?"입니다.
이어지는 것은 저의 해석과 제 의견입니다만, 이것은 데니스 리치가 그의 논문 "C 언어의 발전"에서 설명한 것과 일치합니다.
어레이의 할당 불능은 다음 세 가지 사실에서 발생합니다.
C는 구문론 및 의미론적으로 기계 하드웨어에 근접하도록 설계되어 있습니다.C의 기본 동작은 1개 또는 소수의 기계 명령으로 압축되며, 1개 또는 소수의 프로세서 사이클이 소요됩니다.
어레이는 특히 포인터와 관련된 방식에서 항상 특별했습니다.이 특별한 관계는 C의 이전 언어 B에서의 어레이 처리에서 발전하여 큰 영향을 받았습니다.
구조물은 처음에 C에 있지 않았다.
포인트 2로 인해 어레이를 할당할 수 없습니다.포인트 1로 인해 할당 연산자가 1명이기 때문에 어레이를 할당할 수 없습니다.=
N000 요소 어레이를 복사하는 데 N000 사이클이 소요될 수 있는 코드로 확장해서는 안 됩니다.
그리고 3번 포인트로 넘어가면 모순이 생깁니다.
C가 구조물을 입수했을 때, 그 구조물을 할당하거나 반환할 수 없다는 점에서, 그 구조물도 처음에는 완전히 퍼스트 클래스가 아니었습니다.하지만 그렇게 할 수 없었던 이유는 첫 번째 컴파일러가 처음에는 코드를 생성할 만큼 똑똑하지 않았기 때문입니다.통사적 또는 의미적 장애물은 어레이와 마찬가지로 없었습니다.
그 동안 목표는 구조물이 일급이 되는 것이었고, 이것은 비교적 일찍 이루어졌습니다.컴파일러는 K&R 초판이 인쇄될 무렵에 따라잡아 구조를 할당하고 반환하는 코드를 내보내는 방법을 배웠습니다.
그러나 기본적인 연산이 적은 수의 명령과 사이클로 컴파일되어야 한다면 왜 그 인수가 구조 할당을 허용하지 않는가 하는 의문이 남는다.답은 '그렇다'입니다.그것은 모순입니다.
(저로서는 좀 더 추측이지만) "1등 타입은 좋고 2등 타입은 아쉽다.어레이의 상태는 2등급이지만, 구조를 사용하면 더 잘 할 수 있습니다.무비용 코드 규칙은 규칙이 아니라 가이드라인에 가깝습니다.어레이는 큰 경우가 많지만, 일반적으로 구조는 작거나 수십 바이트 또는 수백 바이트가 되기 때문에 할당하는 데 많은 비용이 들지 않습니다."
그래서 비용이 들지 않는 코드 규칙의 일관된 적용은 물거품이 되었다.어쨌든 C는 완전히 규칙적이거나 일관성이 있는 적이 없다. (그 점에 있어서, 대부분의 성공한 언어는 인공어뿐만 아니라 인간어도 아니다.)
이 모든 것을 고려하면, "C가 어레이의 할당과 반환을 지원했다면?어떻게 하면 좋을까요?그 해답은 식에서 배열의 기본 동작을 비활성화하는 방법, 즉 첫 번째 요소에 대한 포인터로 변환되는 경향이 있어야 합니다.
90년대에 IIRC는 정확히 이 일을 하기 위한 상당히 신중한 제안이 있었습니다.어레이 표현식을 에 포함시킨 것 같습니다.[ ]
또는[[ ]]
뭐 그런 거.오늘은 그 제안에 대한 언급을 찾을 수 없을 것 같습니다(참고를 해 주시면 감사하겠습니다).어쨌든, 이하의 3개의 순서로 어레이를 할당할 수 있도록 C를 확장할 수 있다고 생각합니다.
할당 연산자 왼쪽에 있는 배열을 사용하는 금지를 해제합니다.
어레이 값 함수를 선언하는 금지를 해제합니다.원래 질문으로 돌아가서
char f(void)[8] { ... }
합법적인.(이게 중요한 거야.)식에서 어레이를 언급하고 어레이 유형의 진정한 할당 가능한 값(rvalue)으로 끝나는 방법을 사용합니다.인수를 위해 새로운 연산자 또는 의사함수인
arrayval( ... )
.
[부기사항:현재 어레이/포인트 대응에 대한 "키 정의"가 있습니다.즉, 다음과 같습니다.
식에 나타나는 배열 유형의 객체에 대한 참조는 첫 번째 요소에 대한 포인터로 감소합니다(3가지 예외 포함).
3가지 예외는 어레이가 피연산자일 경우입니다.sizeof
연산자 또는 a&
연산자 또는 문자 배열의 문자열 리터럴 이니셜라이저입니다.여기서 설명하는 가상적인 수정에서는 네 번째 예외가 있습니다.즉, 어레이가 이 새로운 오퍼랜드일 경우입니다.arrayval
오퍼레이터]
어쨌든, 이 수정사항들이 적용되면, 우리는 다음과 같은 것들을 쓸 수 있다.
char a[8], b[8] = "Hello";
a = arrayval(b);
(분명히 우리는 또한 만약 이 문제가 발생한다면 어떻게 해야 할지 결정해야 할 것이다.a
그리고.b
크기가 동일하지 않습니다.)
기능 프로토타입이 주어진 경우
char f(void)[8];
할 수도 있고
a = f();
봅시다f
의 가상의 정의입니다.이런 게 있을 수도 있어요
char f(void)[8] {
char ret[8];
/* ... fill ... */
return arrayval(ret);
}
주의: (가상적인 새로운 것을 제외하고)arrayval()
오퍼레이터) 이것은 Dario Rodriguez가 원래 올린 글에 관한 것입니다.또, 어레이의 할당이 합법이었던 가상 환경에서는, 다음과 같은 점에 주의해 주세요.arrayval()
existed - 이건 정말 효과가 있어요!특히 곧 무효가 될 포인터를 로컬 어레이에 반환하는 문제는 발생하지 않습니다.ret
어레이의 카피가 반환되기 때문에, 전혀 문제가 없습니다.명백히 합법적인 것과 거의 비슷합니다.
int g(void) {
int ret;
/* ... compute ... */
return ret;
}
마지막으로, 「다른 2종류의 타입은 없는가」라고 하는 측면의 질문으로 되돌아가면, 어레이와 같이 기능하고 있는 것은, 기능이나 어레이로서 사용하지 않는 경우에 자동적으로 주소를 취득해, 마찬가지로 기능 타입의 rvalue가 없는 것은 우연이 아니라고 생각합니다.그러나 이것은 대부분 빈둥빈둥 놀고 있는 것입니다. 왜냐하면 C에서 "2급"이라고 불리는 기능은 들어본 적이 없는 것 같습니다.(아마도 들어봤을 테고, 잊어버린 것 같습니다.
각주:컴파일러는 구조를 할당할 용의가 있고, 일반적으로 효율적인 코드를 방출하는 방법을 알고 있기 때문에, 임의의 바이트를 포인트 a에서 포인트 b로 복사하기 위해 컴파일러의 구조 복사 기계를 함께 사용하는 것이 다소 인기 있는 트릭이었습니다.특히, 다음과 같이 다소 이상하게 보이는 매크로를 작성할 수 있습니다.
#define MEMCPY(b, a, n) (*(struct foo { char x[n]; } *)(b) = \
*(struct foo *)(a))
어느 정도 최적화된 인라인 버전처럼 동작하는memcpy()
(실제로 이 트릭은 오늘날에도 컴파일되어 최신 컴파일러에서 작동합니다.)
어레이를 직접 반환하는 것은 무엇입니까?즉, 최적화나 계산의 복잡성이 아니라 구조층 없이 실제로 실행할 수 있는 능력의 관점에서 말입니다.
능력 그 자체와는 무관합니다.다른 언어에서는 어레이를 반환할 수 있습니다.또, C에서는 어레이 멤버를 포함한 구조체를 반환할 수 있습니다.한편, 다른 언어들도 C와 같은 제한을 가지고 있으며, 더 많은 제한을 가지고 있습니다.예를 들어 Java는 메서드에서 어레이를 반환할 수 없으며 실제로 어떤 유형의 개체도 반환할 수 없습니다.오브젝트에 대한 기본 요소와 참조만 반환할 수 있습니다.
아니요, 단순히 언어 디자인에 대한 질문입니다.어레이에 관한 다른 대부분의 작업과 마찬가지로 이 설계점은 어레이 유형의 표현은 거의 모든 컨텍스트에서 포인터로 자동 변환된다는 C의 프로비저닝을 중심으로 합니다.에서 제공되는 값return
스테이트먼트도 예외는 아니기 때문에 C는 어레이 자체의 반환을 표현할 방법도 없습니다.다른 선택을 할 수도 있었지만, 그렇지 않았습니다.
어레이가 퍼스트 클래스 개체일 경우 적어도 어레이를 할당할 수 있어야 합니다.단, 사이즈에 대한 지식이 필요하며, C타입 시스템은 어떤 타입에도 사이즈를 붙일 수 있을 만큼 강력하지 않습니다.C++는 가능하지만, 레거시 문제로 인해 실현되지 않습니다.특정 크기의 어레이에 대한 참조가 있습니다(typedef char (&some_chars)[32]
단, 플레인 배열은 C와 같이 포인터로 암묵적으로 변환됩니다.대신 C++는 std::array를 가지고 있는데, 이는 기본적으로 앞서 언급한 구조 내 배열과 약간의 구문 당을 더한 것입니다.
현상금 사냥.
C의 저자는 언어나 유형 시스템 설계자가 되기를 열망하지 않았다.그들은 공구 디자이너들이었다.C는 시스템 프로그래밍을 쉽게 하기 위한 도구였습니다.참조: C의 Pascal Ritchie의 B Kernighan
특히 UNIX와 C가 덜 놀라운 시대를 열었기 때문에 C가 예상치 못한 일을 할 수 있는 설득력 있는 사례는 없었습니다.배열을 복사하고 복잡한 구문을 만드는 것은 토스트를 굽는 설정이 있는 것과 같은 비유적인 경우 C 모델에 맞지 않습니다.
언어인 C의 모든 것은 사실상 일정한 시간, 일정한 크기입니다.표준인 C는 C의 인기를 끌었던 이 핵심 기능을 폐지하는 데 몰두하고 있는 것 같습니다.따라서 표준인 C/2023. feb07에는 R 값으로 어레이를 사용할 수 있는 구두점 악몽이 있을 것으로 예상됩니다.
프로그래밍 세계를 실용적으로 본다면 C 저자들의 결정은 매우 타당하다.소중한 신념의 설교단이라고 생각하신다면 C/2023.feb07에 탑승하시고 C/2023.feb08.feb08이 무효가 되기 전에 탑승하시기 바랍니다.
유감스럽지만, 제 생각에는 일등이나 이등품에 대한 토론이 아니라, 깊이 있는 임베디드 애플리케이션에 대한 좋은 관행과 적용 가능한 관행에 대한 종교적인 토론입니다.
구조를 반환한다는 것은 콜시퀀스의 깊은 곳에서 은밀하게 변경되거나 데이터의 복제 및 대량의 데이터 청크의 전달에 의해 루트 구조가 변경되는 것을 의미합니다.C의 주요 어플리케이션은 여전히 딥 임베디드 어플리케이션에 집중되어 있습니다.이러한 도메인에는 대량의 데이터 블록을 전달할 필요가 없는 작은 프로세서가 있습니다.또한 동적 RAM 할당 없이 최소 스택과 대부분의 경우 힙 없이 작동할 수 있어야 하는 엔지니어링 실무도 있습니다.구조의 반환은 포인터를 통한 수정과 동일하지만 구문에서 추상화되어 있다고 주장할 수 있습니다.유감스럽게도 그것은 타입에 대한 포인터처럼 "보이는 것은 얻는 것"이라는 C철학에는 들어 있지 않습니다.
개인적으로는 표준 승인 여부와 관계없이 루프홀을 발견하셨다고 생각합니다.C는 할당이 명시적인 방식으로 설계된다.모범 사례의 문제로서 어드레스 버스 크기의 오브젝트(통상은 포부적인 1 사이클)로 전달되며, 이는 개발자의 ken 내에서 제어된 시간에 명시적으로 할당된 메모리를 말합니다.이는 코드 효율, 사이클 효율 측면에서 타당하며 목적에 대한 제어력과 명확성을 극대화합니다.유감스럽게도 코드 검사에서는 구조물을 반환하는 함수를 폐기할 수 있습니다.C는 많은 규칙을 시행하지 않습니다.C는 사용자의 규율에 의존하기 때문에 여러모로 전문 엔지니어를 위한 언어입니다.할 수 있다고 해서 꼭...컴파일 시간의 엄격함과 설치 공간의 동적 변동을 최소화하고 실행 시 매우 복잡한 크기와 유형의 데이터를 처리할 수 있는 몇 가지 확실한 방법을 제공합니다.
언급URL : https://stackoverflow.com/questions/50808782/what-does-impossibility-to-return-arrays-actually-mean-in-c
'itsource' 카테고리의 다른 글
새로운 에러('사이클 의존성' + nodeRep)를 슬로우합니다. (0) | 2022.08.21 |
---|---|
v-if를 사용하여 하위 div를 표시할 때 상위 요소의 높이 점프를 방지하는 방법 (0) | 2022.08.19 |
Vue에서 클래스를 조건부로 추가하려면 어떻게 해야 합니까? (0) | 2022.08.19 |
Laravel Vue Non SPA - vue js 컴포넌트의 모든 페이지의 파일을 분할하려면 어떻게 해야 합니까? (0) | 2022.08.19 |
Apache HttpClient 4.0에서 SSL 인증서 오류를 무시하는 방법 (0) | 2022.08.19 |