유연한 어레이 멤버로 인해 정의되지 않은 동작이 발생할 수 있습니까?
구조 유형 내에서 유연한 배열 구성원(FAM)을 사용함으로써 프로그램을 정의되지 않은 동작 가능성에 노출시키고 있습니까?
프로그램이 FAM을 사용하면서도 엄격하게 준수하는 프로그램이 될 수 있습니까?
플렉시블 어레이 부재의 오프셋은 구조물 끝에 있어야 하나요?
질문은 두 가지 모두에 적용됩니다.C99 (TC3)
그리고.C11 (TC1)
.
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
int main(void) {
struct s {
size_t len;
char pad;
int array[];
};
struct s *s = malloc(sizeof *s + sizeof *s->array);
printf("sizeof *s: %zu\n", sizeof *s);
printf("offsetof(struct s, array): %zu\n", offsetof(struct s, array));
s->array[0] = 0;
s->len = 1;
printf("%d\n", s->array[0]);
free(s);
return 0;
}
출력:
sizeof *s: 16
offsetof(struct s, array): 12
0
단답형
네. FAM을 사용하는 일반적인 관례는 우리 프로그램이 정의되지 않은 행동의 가능성에 노출되어 있습니다.그렇다고 해도, 저는 잘못된 행동을 할 수 있는 기존의 적합한 구현에 대해 알지 못합니다.
가능하지만, 가능성은 없습니다.우리가 실제로 정의되지 않은 행동에 도달하지 않더라도, 우리는 여전히 엄격한 준수에 실패할 가능성이 높습니다.
아니요. FAM의 오프셋이 구조의 끝에 있을 필요는 없으며, 후행 패딩 바이트를 오버랩할 수 있습니다.
답은 두 가지 모두에 적용됩니다.C99 (TC3)
그리고.C11 (TC1)
.
긴 대답
FAM은 C99 (TC0) (1999년 12월)에 처음 도입되었으며, 원래 사양은 구조의 끝에 FAM의 오프셋이 있어야 합니다.원래 사양은 잘 정의되어 있었기 때문에 명확하지 않은 행동으로 이어지거나 엄격한 규정 준수와 관련된 문제가 될 수 없었습니다.
C99 (TC0) §6.7.2.1 p16
(1999년 12월)
[본 문서는 공식 표준이며 저작권이 있으며 자유롭게 이용할 수 없습니다.]
문제는 GCC와 같은 일반적인 C99 구현이 표준의 요구 사항을 따르지 않고 FAM이 후행 패딩 바이트를 오버레이할 수 있도록 허용했다는 것입니다.그들의 접근 방식은 더 효율적이라고 여겨졌고, 그들이 표준의 요구사항을 따르는 것은 역호환성을 깨는 결과를 초래하기 때문에, 위원회는 사양을 변경하기로 결정했고, C99 TC2(2004년 11월) 기준으로 표준은 더 이상 구조의 끝에 FAM의 오프셋을 둘 것을 요구하지 않았습니다.
C99 (TC2) §6.7.2.1 p16
(2004년 11월)
[...] 구조의 크기는 유연한 배열 부재가 생략된 것과 같습니다. 다만 생략이 의미하는 것보다 더 많은 후행 패딩을 가질 수 있다는 점을 제외하고는 말입니다.
새 규격은 구조의 끝에 FAM의 오프셋을 둘 것을 요구하는 문구를 삭제했고, 표준은 구현에 구조 또는 조합 내에서 패딩 바이트의 값을 일관된 상태로 유지하지 않을 자유를 주기 때문에 매우 불행한 결과를 초래했습니다.보다 구체적으로:
C99 (TC3) §6.2.6.1 p6
값이 멤버 개체를 포함한 구조 또는 연합 유형의 개체에 저장되면 패딩 바이트에 해당하는 개체 표현의 바이트가 지정되지 않은 값을 가져옵니다.
이는 FAM 요소 중 하나가 후행 패딩 바이트에 해당(또는 오버레이)하는 경우 구조의 멤버에 저장할 때 불특정 값을 취할 수 있음을 의미합니다.FAM 자체에 저장된 값에 적용되는지 여부에 대해서는 생각해 볼 필요도 없고, 심지어 FAM 이외의 구성원에게만 적용된다는 엄격한 해석도 충분히 피해를 주고 있습니다.
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
int main(void) {
struct s {
size_t len;
char pad;
int array[];
};
struct s *s = malloc(sizeof *s + sizeof *s->array);
if (sizeof *s > offsetof(struct s, array)) {
s->array[0] = 123;
s->len = 1; /* any padding bytes take unspecified values */
printf("%d\n", s->array[0]); /* indeterminate value */
}
free(s);
return 0;
}
구조체의 멤버에 저장하면 패딩 바이트는 지정되지 않은 바이트를 사용하므로 후행 패딩 바이트에 해당하는 FAM 요소의 값에 대한 가정은 이제 거짓이 됩니다.그 말은 어떤 가정도 우리가 엄격한 순응을 하지 못하게 한다는 뜻입니다.
미정의 행위
패딩 바이트의 값이 "지정되지 않은 값"이지만, 지정되지 않은 값을 기반으로 하는 객체 표현은 트랩 표현을 생성할 수 있기 때문에, 패딩 바이트의 값이 "지정되지 않은 값"이라고는 할 수 없습니다.따라서 이 두 가지 가능성을 설명하는 유일한 표준 용어는 "불확정한 값"일 것입니다.FAM 유형에 트랩 표현이 있는 경우 FAM에 액세스하는 것은 단순히 지정되지 않은 값의 문제가 아니라 정의되지 않은 동작입니다.
하지만 잠깐만, 더 있어요.만약 우리가 그러한 값을 설명할 수 있는 유일한 표준 용어가 "불확정한 값"이라는 것에 동의한다면, FAM의 유형이 트랩 표현을 갖지 않더라도, 우리는 정의되지 않은 행동에 도달한 것입니다.C 표준 위원회의 공식적인 해석은 불확정한 값을 표준 라이브러리 함수에 전달하는 것은 정의되지 않은 행동이기 때문입니다.
이것은 까다로운 주제를 광범위하게 다루는 긴 답변입니다.
TL;DR
핵심 문제는 C99 및 C11 표준의 §6.2.16이 무엇을 의미하는지 잘못 이해하고, 다음과 같은 단순한 정수 할당에 부적절하게 적용하는 것입니다.
fam_ptr->nonfam_member = 23;
이 할당은 다음과 같이 가리키는 구조의 패딩 바이트를 변경할 수 없습니다.fam_ptr
. 따라서 이것이 구조 내 패딩 바이트를 변화시킬 수 있다는 가정에 근거한 분석은 잘못된 것입니다.
배경
원칙적으로 C99 표준과 그 코리젠다에 대해서는 크게 우려하지 않습니다. 현재 표준이 아닙니다.그러나 유연한 배열 부재의 사양의 진화는 유익합니다.
C99 표준(ISO/IEC 9899:1999)에는 다음과 같은 3가지 기술적 코리젠다가 있습니다.
- TC1 2001-09-01 공개 (7페이지),
- TC2 2004-11-15 공개 (15페이지),
- TC3 2007-11-15 공개 (10페이지)
예를 들어 다음과 같이 진술한 것은 TC3입니다.gets()
이는 낡은 것으로 평가절하되어 C11 표준에서 삭제되었습니다.
C11 표준(ISO/IEC 9899:2011)에는 하나의 기술적 코리젠덤이 있지만, 단순히 실수로 두 개의 매크로가 형태로 남겨지는 값을 설정합니다.__STDC_VERSION__
그리고.__STDC_LIB_EXT1__
값으로 정정되었습니다.201112L
. (https://www.iso.org/obp/ui/ #iso:std:iso-iec:9899:2011/Cor.1:2012(en) Information Technology — Programming languages — C TECHAL CORRIGENDUM 1")에서 TC1을 볼 수 있습니다.어떻게 다운로드를 받으시는지 알아내지는 못했지만, 너무 간단해서 별 문제가 되지 않습니다.
유연한 배열 부재에 관한 C99 표준
ISO/IEC 9899:1999 (TC2 이전) §6.7.2.116:
특수한 경우, 둘 이상의 명명된 멤버를 가진 구조의 마지막 요소는 불완전한 배열 유형을 가질 수 있습니다. 이를 유연한 배열 멤버라고 합니다.두 가지 예외를 제외하고 유연한 배열 구성원은 무시됩니다.첫째, 구조물의 크기는 플렉서블 어레이 부재를 불특정 길이의 어레이로 대체하는 동일한 구조물의 마지막 요소의 오프셋과 동일해야 합니다.106)두번째로, a일때.
.
(또는->
연산자는 왼쪽 피연산자를 갖는데, 그것은 유연한 배열 부재를 가진 구조와 그 부재의 이름을 가진 오른쪽 피연산자를 가지고 있는데, 그것은 마치 그 부재가 접근하는 물체보다 구조를 더 크게 만들지 않는 가장 긴 배열(같은 원소 유형을 가진)로 대체된 것처럼 행동합니다. 배열의 오프셋은 다음의 오프셋을 유지해야 합니다.이것이 대체 배열의 그것과 다를지라도, e flexible array member.이 배열에 요소가 없으면 하나의 요소가 있는 것처럼 동작하지만 해당 요소에 액세스하거나 해당 요소를 지나 한 번 포인터를 생성하려고 하면 동작이 정의되지 않습니다.126) 길이는 구현 시 길이에 따라 배열 구성원이 서로 다른 정렬을 제공할 수 있다는 사실을 감안하여 지정되지 않습니다.
(이 각주는 다시 쓰기에서 삭제됩니다.)최초의 C99 표준은 다음과 같은 한 가지 예를 포함합니다.
17 예시 모든 배열 구성원이 동일하게 정렬되었다고 가정할 때, 선언 후 다음 사항을 충족합니다.
struct s { int n; double d[]; }; struct ss { int n; double d[1]; };
세가지 표현:
sizeof (struct s) offsetof(struct s, d) offsetof(struct ss, d)
같은 가치를 가지다구조물은 유연한 배열 부재(d)를 갖습니다.
18 (double)의 크기가 8인 경우, 다음 코드가 실행된 후:
struct s *s1; struct s *s2; s1 = malloc(sizeof (struct s) + 64); s2 = malloc(sizeof (struct s) + 46);
malloc에 대한 호출이 성공했다고 가정하면 s1과 s2가 가리키는 개체는 식별자가 선언된 것처럼 동작합니다.
struct { int n; double d[8]; } *s1; struct { int n; double d[5]; } *s2;
19 이후 성공적인 과제 수행:
s1 = malloc(sizeof (struct s) + 10); s2 = malloc(sizeof (struct s) + 6);
그런 다음 선언이 다음과 같은 것처럼 행동합니다.
struct { int n; double d[1]; } *s1, *s2;
그리고:
double *dp; dp = &(s1->d[0]); // valid *dp = 42; // valid dp = &(s2->d[0]); // valid *dp = 42; // undefined behavior
20 과제:
*s1 = *s2;
구성원 n만 복사하고 배열 요소는 복사하지 않습니다.마찬가지로:
struct s t1 = { 0 }; // valid struct s t2 = { 2 }; // valid struct ss tt = { 1, { 4.2 }}; // valid struct s t3 = { 1, { 4.2 }}; // invalid: there is nothing for the 4.2 to initialize t1.n = 4; // valid t1.d[0] = 4.2; // undefined behavior
이 예시적인 재료의 일부는 C11에서 제거되었습니다.TC2에서는 사례가 규범적이지 않기 때문에 변경 사항을 기록하지 않았습니다(그리고 기록할 필요도 없었습니다).그러나 C11의 개작된 자료는 연구할 때 유익합니다.
유연한 어레이 멤버의 문제를 식별하는 N983 논문
WG14 Pre-Santa Cruz-2002 메일의 N983은 결함 보고의 최초 진술이라고 생각합니다.일부 C 컴파일러(3개 인용)는 구조의 끝에 있는 패딩 앞에 FAM을 넣을 수 있다고 말합니다.최종 불량 보고는 DR 282였습니다.
제가 이해하는 바로는, 이 보고서가 TC2의 변경으로 이어졌지만, 그 과정의 모든 단계를 추적한 것은 아닙니다.DR은 더 이상 별도로 사용할 수 없는 것으로 보입니다.
TC2는 표준 자료에서 C11 표준에 나와 있는 문구를 사용하였습니다.
유연한 배열 부재에 관한 C11 표준
그렇다면 C11 표준은 유연한 어레이 멤버에 대해 무엇을 말하는가?
§6.7.2.1 구조물 및 유니언 규격
3 구조 또는 조합은 불완전하거나 기능적인 유형을 가진 구성원(따라서, 구조는 자신의 인스턴스를 포함하지 않아야 하지만, 자신의 인스턴스에 대한 포인터를 포함할 수 있음)을 포함할 수 없습니다. 단, 둘 이상의 명명된 구성원을 가진 구조의 마지막 구성원이 불완전한 배열 유형을 가질 수 있는 경우를 제외하고는. 이러한 구조(그리고 po를 포함하는 모든 조합).재귀적으로, 그러한 구조인 부재)는 구조의 부재이거나 배열의 요소가 되어서는 안 됩니다.
이를 통해 FAM이 구조의 끝에 확실하게 배치됩니다. '마지막 멤버'는 정의에 따라 구조의 끝에 있으며, 이는 다음을 통해 확인됩니다.
15 구조 객체 내에서, 비-비트 필드 멤버들과 비트 필드들이 위치하는 유닛들은 그것들이 선언되는 순서대로 증가하는 어드레스들을 갖습니다.
17 구조나 조합의 끝에 이름없는 패딩이 있을 수 있습니다.
18 특별한 경우로서, 둘 이상의 명명된 부재를 갖는 구조의 마지막 요소는 불완전한 배열 형태를 가질 수 있고, 이를 플렉서블 배열 부재라고 합니다.대부분의 경우 유연한 배열 부재는 무시됩니다.특히, 구조의 크기는 마치 유연한 배열 부재가 생략된 것처럼, 생략이 의미하는 것보다 더 많은 후행 패딩을 가질 수도 있습니다.하지만, 언제.
.
(또는->
연산자는 왼쪽 피연산자를 갖는데, 그것은 유연한 배열 부재를 가진 구조와 그 부재의 이름을 가진 오른쪽 피연산자를 가지고 있는데, 그것은 마치 그 부재가 접근하는 물체보다 구조를 더 크게 만들지 않는 가장 긴 배열(같은 원소 유형을 가진)로 대체된 것처럼 행동합니다. 배열의 오프셋은 다음의 오프셋을 유지해야 합니다.이것이 대체 배열의 그것과 다를지라도, e flexible array member.이 배열에 요소가 없으면 하나의 요소가 있는 것처럼 동작하지만 해당 요소에 액세스하거나 해당 요소를 지나 한 번 포인터를 생성하려고 하면 동작이 정의되지 않습니다.
이 단락은 ISO/IEC 9899:1999/Cor.2:2004(E))의 20의 변경사항을 포함하고 있습니다 — C99에 대한 TC2.
유연한 배열 부재를 포함하는 구조의 주요 부분 끝에 있는 데이터는 임의의 구조 유형에서 발생할 수 있는 규칙적인 후행 패딩입니다.이러한 패딩은 합법적으로 접근할 수 없지만 정의되지 않은 동작을 야기하지 않고 구조에 대한 포인터를 통해 라이브러리 함수 등으로 전달될 수 있습니다.
C11 표준은 세 가지 예를 포함하고 있지만 첫 번째와 세 번째는 유연한 배열 부재의 역학이 아닌 익명의 구조와 결합과 관련이 있습니다.예는 '규범적'인 것은 아니지만, 실례가 된다는 것을 기억하세요.
20 사례 2 선언 후:
struct s { int n; double d[]; };
구조
struct s
유연한 배열 멤버를 갖습니다.d
. 일반적인 사용 방법은 다음과 같습니다.int m = /* some value */; struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));
그리고 그 전화가
malloc
개체가 가리키는 success,p
대부분의 목적을 위해 마치 행동하는 것처럼 행동합니다.p
다음과 같이 선언되었습니다.struct { int n; double d[m]; } *p;
(이 동등성이 깨지는 경우가 있습니다. 특히 멤버의 오프셋이
d
동일하지 않을 수 있습니다.21 위 선언에 따를 경우:
struct s t1 = { 0 }; // valid struct s t2 = { 1, { 4.2 }}; // invalid t1.n = 4; // valid t1.d[0] = 4.2; // might be undefined behavior
초기화는
t2
유효하지 않습니다(및 제약 조건 위반).struct s
구성원이 포함되지 않은 것처럼 처리됩니다.d
. 에의 과제.t1.d[0]
아마도 정의되지 않은 행동일 것입니다. 하지만 가능한 것은sizeof (struct s) >= offsetof(struct s, d) + sizeof (double)
그런 경우에 그 일은 합법적일 것입니다.그럼에도 불구하고 엄격하게 일치하는 코드로 표시될 수는 없습니다.
22 추가 신고 후:
struct ss { int n; };
수식:
sizeof (struct s) >= sizeof (struct ss) sizeof (struct s) >= offsetof(struct s, d)
항상 1과 같습니다.
23 만약
sizeof (double)
는 8이며, 다음 코드가 실행된 후입니다.struct s *s1; struct s *s2; s1 = malloc(sizeof (struct s) + 64); s2 = malloc(sizeof (struct s) + 46);
전화를 받은 사람들이
malloc
성공하다, 가 가리키는 대상들.s1
그리고.s2
대부분의 목적을 위해 식별자가 다음과 같이 선언된 것처럼 행동합니다.struct { int n; double d[8]; } *s1; struct { int n; double d[5]; } *s2;
24 이후 성공적인 과제 수행:
s1 = malloc(sizeof (struct s) + 10); s2 = malloc(sizeof (struct s) + 6);
그런 다음 선언이 다음과 같은 것처럼 행동합니다.
struct { int n; double d[1]; } *s1, *s2;
그리고:
double *dp; dp = &(s1->d[0]); // valid *dp = 42; // valid dp = &(s2->d[0]); // valid *dp = 42; // undefined behavior
25 과제:
*s1 = *s2;
멤버 복사만 가능합니다.
n
; 배열 요소 중 하나라도 첫 번째 요소 안에 있으면sizeof (struct s)
구조체의 바이트는 복사되거나 단순히 불확정한 값으로 덮어쓸 수 있습니다.
이것은 C99와 C11 사이에서 바뀌었다는 것에 유의하세요.
표준의 또 다른 부분은 다음과 같은 복사 동작에 대해 설명합니다.
§6.2.6 형식의 표현 §6.2.6.1 일반
6 값이 구성원 개체를 포함한 구조 또는 연합 유형의 개체에 저장될 때, 임의의 패딩 바이트에 해당하는 개체 표현의 바이트는 지정되지 않은 값을 취합니다.51)구조물 또는 조합 객체의 구성원 값이 트랩 표현일 수 있음에도 불구하고 구조물 또는 조합 객체의 값은 결코 트랩 표현이 아닙니다.
51) 따라서 예를 들어 구조 할당은 패딩 비트를 복사할 필요가 없습니다.
문제가 있는 FAM 구조 설명
C 채팅방에서 몇 가지 정보를 적었는데, 그 내용은 다음과 같습니다.
고려 사항:
struct fam1 { double d; char c; char fam[]; };
더블이 8바이트 정렬(또는 4바이트 정렬)을 필요로 한다고 가정하면 크게 상관없지만 8로 유지하겠습니다.struct non_fam1a { double d; char c; };
다음에 7 패딩 바이트를 가질 것입니다.c
그리고 16사이즈입니다.더,struct non_fam1b { double d; char c; char nonfam[4]; };
3바이트 패딩이 있을 것입니다.nonfam
배열, 그리고 크기는 16.
그 제안은 그들의 시작이fam
인에struct fam1
오프셋 9에 있을 수 있음에도 불구하고.sizeof(struct fam1)
16입니다. 그럼 그 다음 바이트들은c
(necess적으로) 패딩이 아닙니다.
따라서 충분히 작은 FAM의 경우 구조의 크기와 FAM의 크기는 여전히 다음의 크기보다 작을 수 있습니다.struct fam
.
프로토타입 할당은 다음과 같습니다.
struct fam1 *fam = malloc(sizeof(struct fam1) + array_size * sizeof(char));
팸이 유형일 때char
(에서와 같이)struct fam1
팸의 오프셋이 다음보다 작을 때 이는 (총) 과대 추정치입니다.sizeof(struct fam1)
.
구조의 크기보다 작은 FAM 오프셋을 기반으로 '정확한' 필요 스토리지를 계산하기 위한 매크로가 있습니다.이것과 같은 것: https://gustedt.wordpress.com/2011/03/14/flexible-array-member/
문제 해결
질문은 다음과 같습니다.
- 구조 유형 내에서 유연한 배열 구성원(FAM)을 사용함으로써 프로그램을 정의되지 않은 동작 가능성에 노출시키고 있습니까?
- 프로그램이 FAM을 사용하면서도 엄격하게 준수하는 프로그램이 될 수 있습니까?
- 플렉시블 어레이 부재의 오프셋은 구조물 끝에 있어야 하나요?
질문은 C99(TC3)와 C11(TC1)에 모두 적용됩니다.
코드를 정확히 맞추면 정답은 "아니오", "예", "아니오 그리고 예, 그에 따라…"입니다.
질문1
질문 1의 의도는 "FAM을 어디서나 사용할 경우 프로그램이 반드시 정의되지 않은 행동에 노출되어야 하는가?"라고 가정합니다.제가 생각하는 것은 명백합니다. 프로그램을 정의되지 않은 동작에 노출시키는 많은 방법들이 있습니다. 그리고 그러한 방법들 중 일부는 유연한 어레이 멤버를 가진 구조를 포함합니다.
단순히 FAM을 사용하는 것이 프로그램이 정의되지 않은 동작을 자동으로(발호하고, 노출됨) 한다는 것을 의미한다고 생각하지 않습니다.
질문2
섹션 §4 적합성은 다음을 정의합니다.
5 엄격하게 준수하는 프로그램은 이 국제 표준에 명시된 언어와 라이브러리의 특징만을 사용해야 합니다.3)특정되지 않거나, 정의되지 않거나, 구현이 정의된 동작에 의존하는 출력을 생성해서는 안 되며, 최소 구현 한도를 초과해서는 안 됩니다.
3) 엄격하게 준수하는 프로그램은 관련 매크로를 사용하는 적절한 조건부 포함 전처리 지시에 의해 사용이 보호된다면 조건부 기능(6.10.8.3 참조)을 사용할 수 있습니다. …
7 적합한 프로그램은 적합한 구현에 적합한 프로그램입니다.5)
5) 엄격하게 준수하는 프로그램은 준수하는 구현 중에서 최대한 휴대성을 갖추도록 고안되었습니다.적합한 프로그램은 적합한 구현의 휴대용이 아닌 기능에 따라 달라질 수 있습니다.
표준 C에는 표준이 의도하는 방식으로 사용될 경우 프로그램이 엄격하게 준수되지 않는 특징이 없다고 생각합니다.그런 것이 있으면 로케일 의존적인 동작과 관련이 있습니다.FAM 코드의 동작은 본질적으로 로케일에 의존하지 않습니다.
저는 FAM의 사용이 본질적으로 프로그램이 엄격하게 준수되지 않는다는 것을 의미한다고 생각하지 않습니다.
질문3
3번 문제는 다음과 같은 것들 사이에서 모호하다고 생각합니다.
- 3A: 플렉서블 어레이 부재의 오프셋이 플렉서블 어레이 부재가 포함된 구조물의 크기와 동일해야 하나요?
- 3B: 플렉시블 어레이 부재의 오프셋이 구조물의 이전 부재의 오프셋보다 커야 합니까?
3A에 대한 답은 "아니오"입니다(위에서 인용한 25번 C11 예 참조).
3B에 대한 답은 "예"입니다(증인 §6.7.2.115, 위에서 인용).
닥터의 대답에 반대하는 입장
C 표준과 닥터의 답변을 인용해야 합니다.쓸게요[DK]
Dor의 답변에서 인용문의 시작을 나타내는 것이며, 표시되지 않은 인용문은 C 표준에서 가져온 것입니다.
2017-07-01 18:00 - 08:00 현재 닥터 K의 단답형은 다음과 같습니다.
[DK]
- 네. FAM을 사용하는 일반적인 관례는 우리 프로그램이 정의되지 않은 행동의 가능성에 노출되어 있습니다.그렇다고 해도, 저는 잘못된 행동을 할 수 있는 기존의 적합한 구현에 대해 알지 못합니다.
단순히 FAM을 사용하는 것이 프로그램이 자동적으로 정의되지 않은 동작을 갖는다는 것을 의미하는지 확신할 수 없습니다.
[DK]
- 가능하지만, 가능성은 없습니다.우리가 실제로 정의되지 않은 행동에 도달하지 않더라도, 우리는 여전히 엄격한 준수에 실패할 가능성이 높습니다.
저는 FAM을 사용하면 프로그램이 자동적으로 엄격하게 준수되지 않는다고 확신하지 못합니다.
[DK]
- 아니요. FAM의 오프셋이 구조의 끝에 있을 필요는 없으며, 후행 패딩 바이트를 오버랩할 수 있습니다.
이것은 제 해석 3A에 대한 답변이며, 저는 이에 동의합니다.
긴 답변에는 위의 짧은 답변에 대한 해석이 포함되어 있습니다.
[DK]
문제는 GCC와 같은 일반적인 C99 구현이 표준의 요구 사항을 따르지 않고 FAM이 후행 패딩 바이트를 오버레이할 수 있도록 허용했다는 것입니다.그들의 접근 방식은 더 효율적이라고 여겨졌고, 그들이 표준의 요구사항을 따르는 것은 역호환성을 깨는 결과를 초래하기 때문에, 위원회는 사양을 변경하기로 결정했고, C99 TC2(2004년 11월) 기준으로 표준은 더 이상 구조의 끝에 FAM의 오프셋을 둘 것을 요구하지 않았습니다.
저는 이 분석에 동의합니다.
[DK]
새 규격은 구조의 끝에 FAM의 오프셋을 둘 것을 요구하는 문구를 삭제했고, 표준은 구현에 구조 또는 조합 내에서 패딩 바이트의 값을 일관된 상태로 유지하지 않을 자유를 주기 때문에 매우 불행한 결과를 초래했습니다.
저는 새로운 명세서에서 FAM을 구조물의 크기 이상의 오프셋으로 저장해야 한다는 요구사항이 삭제되었다는 것에 동의합니다.
저는 패딩 바이트에 문제가 있다는 것에 동의하지 않습니다.
표준은 FAM을 포함하는 구조물에 대한 구조 할당이 효과적으로 FAM을 무시한다고 명시적으로 말합니다( §6.7.2.118).FAM이 아닌 구성원을 복사해야 합니다.패딩 바이트는 전혀 복사할 필요가 없다고 명시되어 있습니다( §6.2.6.16 및 각주 51).그리고 예제 2는 FAM이 구조물에 의해 정의된 공간과 중복되는 경우 구조물의 끝과 중복되는 FAM 부분의 데이터가 복사되거나 복사되지 않을 수 있음을 명시적으로 명시합니다(비규범적으로 §6.7.2.125).
[DK]
이는 FAM 요소 중 하나가 후행 패딩 바이트에 해당(또는 오버레이)하는 경우 구조의 멤버에 저장할 때 불특정 값을 취할 수 있음을 의미합니다.FAM 자체에 저장된 값에 적용되는지 여부에 대해서는 생각해 볼 필요도 없고, 심지어 FAM 이외의 구성원에게만 적용된다는 엄격한 해석도 충분히 피해를 주고 있습니다.
저는 이것이 문제가 되지 않는다고 봅니다.구조 할당을 사용하여 FAM이 포함된 구조를 복사하고 FAM 배열을 복사할 수 있다는 기대는 본질적으로 결함이 있습니다. 즉, 복사를 통해 FAM 데이터가 논리적으로 복사되지 않게 됩니다.구조 범위 내에서 FAM 데이터에 의존하는 모든 프로그램은 손상됩니다. 이는 표준이 아닌 (결함된) 프로그램의 속성입니다.
[DK]
#include <stdio.h> #include <stdlib.h> #include <stddef.h> int main(void) { struct s { size_t len; char pad; int array[]; }; struct s *s = malloc(sizeof *s + sizeof *s->array); if (sizeof *s > offsetof(struct s, array)) { s->array[0] = 123; s->len = 1; /* any padding bytes take unspecified values */ printf("%d\n", s->array[0]); /* indeterminate value */ } free(s); return 0; }
물론 이상적으로, 코드는 이름난 멤버를 설정할 것입니다.pad
결정적인 값을 제공하지만 액세스되지 않기 때문에 실제로 문제가 발생하지는 않습니다.
저는 그 가치에 대해 단호히 동의하지 않습니다.s->array[0]
에서printf()
불확정적이다; 그것의 값은123
.
이전 표준 인용문은 다음과 같습니다(C99와 C11 모두에서 동일한 §6.2.6.16이지만 각주 번호는 C99에서 42, C11에서 51).
값이 멤버 개체를 포함한 구조 또는 연합 유형의 개체에 저장되면 패딩 바이트에 해당하는 개체 표현의 바이트가 지정되지 않은 값을 가져옵니다.
참고:s->len
구조 또는 조합 유형의 개체에 대한 할당이 아닙니다. 유형의 개체에 대한 할당입니다.size_t
. 이것이 여기 혼란의 주요 원인이 될 수 있다고 생각합니다.
코드가 포함된 경우:
struct s *t = malloc(sizeof(*t) + sizeof(t->array[0]));
*t = *s;
printf("t->array[0] = %d\n", t->array[0]);
그러면 인쇄된 값은 실제로 불확실합니다.그러나 FAM으로 구조를 복사하는 것이 FAM을 복사하는 것을 보장하지 않기 때문입니다.(추가한다고 가정하면) 더 정확한 코드가 될 것입니다.#include <string.h>
, 물론):
struct s *t = malloc(sizeof(*t) + sizeof(t->array[0]));
*t = *s;
memmmove(t->array, s->array, sizeof(t->array[0]));
printf("t->array[0] = %d\n", t->array[0]);
이제 인쇄된 값이 결정됩니다(그것은123
). 의 상태에 유의합니다.if (sizeof *s > offsetof(struct s, array))
제 분석에는 중요하지 않습니다.
긴 답변의 나머지 부분(주로 '정의되지 않은 행동'이라는 제목으로 식별되는 부분)은 구조의 정수 멤버에 할당할 때 구조의 패딩 바이트가 변경될 가능성에 대한 잘못된 추론에 기반하기 때문에 나머지 논의는 추가 분석이 필요하지 않습니다.
[DK]
구조체의 멤버에 저장하면 패딩 바이트는 지정되지 않은 바이트를 사용하므로 후행 패딩 바이트에 해당하는 FAM 요소의 값에 대한 가정은 이제 거짓이 됩니다.그 말은 어떤 가정도 우리가 엄격한 순응을 하지 못하게 한다는 뜻입니다.
이것은 잘못된 전제에 근거한 것입니다. 결론은 거짓입니다.
(거의 모든 종류의 유용한 출력이 실행 문자 집합과 같은 구현 정의 세부 정보에 의존함에도 불구하고) 모든 합법적인 동작으로 "작동"하는 경우, 엄격하게 준수하는 프로그램이 구현 정의된 동작을 사용하도록 허용한다면,프로그램이 유연한 배열 부재의 오프셋이 구조물의 길이와 일치하는지 여부에 상관하지 않는다면 엄격하게 준수하는 프로그램 내에서 유연한 배열 부재의 사용이 가능해야 합니다.
배열은 내부적으로 패딩이 있는 것으로 간주되지 않으므로 FAM으로 인해 추가되는 패딩이 선행됩니다.구조 내 또는 구조 너머에 FAM의 구성원을 수용할 수 있는 충분한 공간이 있는 경우, 해당 구성원은 FAM의 일부입니다.예를 들어, 주어진 경우:
struct { long long x; char y; short z[]; } foo;
"foo"의 크기는 시작을 넘어 덧대어져 있을 수 있습니다.z
정렬 때문에, 그러나 그러한 패딩은 일부로서 사용될 것입니다.z
.쓰기y
앞에 오는 패딩을 방해할 수 있습니다.z
, 그러나 어떤 부분도 방해하지 않아야 합니다.z
그 자체.
언급URL : https://stackoverflow.com/questions/44745677/flexible-array-members-can-lead-to-undefined-behavior
'it-source' 카테고리의 다른 글
워드프레스 웹사이트에서 json 응답을 얻는 방법 (0) | 2023.11.02 |
---|---|
스프링 프로토타입 범위 - 사용 사례? (0) | 2023.11.02 |
반환된 변수의 기억을 자유롭게 하는 적절한 방법 (0) | 2023.11.02 |
어린이 중 한 명이 포커스를 받을 경우 블러 이벤트가 발생하지 않도록 합니다. (0) | 2023.11.02 |
Using RepositoryRestResource annotation to change RESTful endpoint not working (0) | 2023.11.02 |