"구조 해킹"은 기술적으로 정의되지 않은 행동인가?
내가 묻고 싶은 것은 잘 알려진 "구조체의 마지막 구성원은 길이가 가변적인" 속임수다.이런 식으로 흘러간다.
struct T {
int len;
char s[1];
};
struct T *p = malloc(sizeof(struct T) + 100);
p->len = 100;
strcpy(p->s, "hello world");
구조물이 메모리에 배치되는 방식 때문에 우리는 필요 이상으로 큰 블록 위에 구조물을 덧씌우고 마지막 구성원을 보다 큰 블록으로 취급할 수 있다.1 char
명시된
그래서 질문은: 이 기술이 기술적으로 정의되지 않은 행동인가?그럴 줄 알았는데, 기준이 뭐라고 하는지 궁금했다.
PS: 나는 이것에 대한 C99 접근법을 알고 있다. 나는 위에 열거된 속임수의 버전에 대한 해답을 구체적으로 고수하고 싶다.
그렇다, 그것은 정의되지 않은 행동이다.
C 언어 결함 보고서 #051은 이 질문에 대한 명확한 답변을 제공한다.
그 관용구는 흔하지만 엄밀하게 따지지 않는다.
http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_051.html
C99 근거 문서에서 C 위원회는 다음과 같이 덧붙인다.
이 구조의 타당성은 항상 의심스러웠다.하나의 결함 보고서에 대한 응답으로, 위원회는 공간 존재 여부에 관계없이 배열 p->아이템이 하나의 항목만 포함하기 때문에 정의되지 않은 행동이라고 결정했다.
C FAQ에 따르면 다음과 같다.
합법적인지 휴대할 수 있는지는 분명하지 않지만, 오히려 인기가 있다.
및:
... 공식 해석은 C 표준이 알려진 모든 구현에서 효과가 있는 것처럼 보이지만, 엄격하게 C 표준을 준수하지 않는다고 간주했다. (배열 한계를 주의 깊게 점검하는 평가자는 경고를 발령할 수 있다.)
'엄격한 준수' 비트의 근거는 정의되지 않은 동작 목록에 포함된 섹션 J.2 정의되지 않은 동작의 규격에 있다.
- lvalue 식과 같이 지정된 첨자로 개체에 액세스할 수 있는 것으로 보이는 경우에도 어레이 첨자가 범위를 벗어남
a[1][7]
라고 하면.int a[4][5]
) (6.5.6).
6.5.6절 제8항 적층 운영자는 정의된 배열 범위를 벗어난 액세스가 정의되지 않았다는 또 다른 언급이 있다.
포인터가 피연산자와 결과가 모두 동일한 배열 객체의 요소를 가리키거나 배열 객체의 마지막 요소를 1회 지나 가리키는 경우, 평가는 오버플로를 발생시키지 않아야 한다. 그렇지 않으면 동작이 정의되지 않는다.
나는 기술적으로 그것은 정의되지 않은 행동이라고 믿는다.표준(논의할 수 있음)은 그것을 직접적으로 다루지 않기 때문에, 그것은 정의되지 않은 행동이라고 하는 "또는 행동의 명시적 정의의 누락에 의해서" 조항(C99의 제4/2조, C89의 제3.16/2조)에 해당된다.
위의 "논의할 만한" 것은 배열 첨자 연산자의 정의에 달려 있다.구체적으로, "정사각형 대괄호 []의 표현 뒤에 오는 사후 수식은 배열 개체의 첨자 지정이다."(C89, §6.3.2.1/2)라고 되어 있다.
여기서 "배열 객체의"가 위반되고 있다고 주장할 수 있으며(배열 객체의 정의된 범위를 벗어나는 첨자이기 때문에), 이 경우 동작은 (조금 더) 명시적으로 정의되지 않은 것이며, 정의되는 것이 전혀 없는 정의되지 않은 예의일 뿐이다.
이론적으로, 나는 배열 한계 확인을 하는 컴파일러를 상상할 수 있다. 그리고 (예를 들어) 당신이 범위를 벗어난 첨자를 사용하려고 할 때 프로그램을 중단시킬 것이다.사실, 나는 그런 것이 존재하는지는 잘 모르고, 이런 식의 코드의 인기에 비추어 볼 때, 컴파일러가 어떤 상황에서 첨자를 강요하려고 해도, 이런 상황에서 누가 그것을 참아낼지 상상하기 어렵다.
그러한 특정한 방법은 C 표준에 명시적으로 규정되어 있지 않지만, C99는 언어의 일부로서 "구조 해킹"을 포함하고 있다.C99에서 구조물의 마지막 멤버는 다음과 같이 선언된 "유연한 어레이 멤버"일 수 있다.char foo[]
(어떤 타입을 대신해서 원하는지)char
).
그렇다, 그것은 기술적으로 정의되지 않은 행동이다.
참고로, "구조 해킹"을 구현하는 방법에는 다음 세 가지가 있다.
(1) 크기 0(레거시 코드에서 가장 "대중" 방식)으로 후행 배열을 선언한다.제로 사이즈 배열 선언은 C에서 항상 불법이기 때문에 이것은 분명히 UB이다.그것이 컴파일한다고 해도, 언어는 어떤 제약조건 위반 코드의 행동에 대해 보증하지 않는다.
(2) 최소한의 법적 크기로 배열 선언 - 1 (당신의 경우)이 경우 포인터로 전환하려는 시도가 있을 경우p->s[0]
그리고 그것을 넘어서는 포인터 산술에 사용한다.p->s[1]
정의되지 않은 행동이야예를 들어, 디버깅 구현은 범위 정보가 포함된 특수 포인터를 생성할 수 있도록 허용되며, 이 포인터는 범위를 넘어 포인터를 만들 때마다 트랩된다.p->s[1]
.
(3) 예를 들어, 10000과 같은 "매우 큰" 크기의 배열을 선언한다.그 아이디어는 선언된 크기가 실제 연습에서 필요한 것보다 더 커야 한다는 것이다.이 방법은 어레이 액세스 범위와 관련하여 UB가 없다.그러나, 실제로, 물론, 우리는 항상 더 적은 양의 메모리를 할당할 것이다(정말로 필요한 만큼만).나는 이것의 합법성에 대해 확신할 수 없다.개체의 선언된 크기보다 개체에 더 적은 메모리를 할당하는 것이 얼마나 합법적인지 궁금하다("할당되지 않은" 멤버에 결코 접근하지 않는다고 가정할 때).
표준은 배열의 끝 이외에는 접속할 수 없다는 것이 아주 명확하다.(그리고 포인터들을 통해 이동하는 것은 도움이 되지 않는다. 왜냐하면 당신은 어레이가 끝난 후 포인터들을 한 개도 증가시킬 수 없기 때문이다.)
그리고 "실무에서 일"을 위해.나는 이 부적합한 C를 만날 때 이 표준 부분을 사용하여 잘못된 코드를 생성하는 gcc/g++ 최적기를 본 적이 있다.
그것은 표준에 의해 정의되기 때문에, 공식적이든 다른 사람이든 간에 어떤 말을 하든 정의되지 않은 행동이 아니다. p->s
, l값으로 사용되는 경우를 제외하고, 다음과 동일한 포인터로 평가한다.(char *)p + offsetof(struct T, s)
. 특히 이것은 유효하다.char
malloc'd 객체 내부에 포인터, 그리고 포인터 바로 다음에 이어지는 100개(또는 그 이상, 정렬 고려사항에 따라 결정됨)의 연속적인 주소가 있으며, 이 주소는 또한 다음과 같이 유효하다.char
할당된 개체 내의 개체.포인터에서 다음을 사용하여 파생된 사실->
에 의해 반환된 포인터에 오프셋을 명시적으로 추가하는 대신malloc
, 에 캐스팅하다.char *
는 관련이 없다.
엄밀히 말하면p->s[0]
의 다.char
구조체 내부의 배열, 다음 몇 가지 요소(예:p->s[1]
을 통해p->s[3]
이 높으며,될 수 만 하면 안 사항 )을 할 수 이다. 바이트일 가능성이 높으며, 구조체 전체에 대한 할당을 수행하면 손상될 수 있지만 개별 구성원에 액세스하기만 하면 안 되며, 나머지 요소는 정렬 요구 사항을 준수하는 한, 사용자가 원하는 대로 자유롭게 사용할 수 있는 할당된 개체의 추가 공간이다(및char
정렬 요구사항이 없음).
구조물의 패딩 바이트와 중복될 가능성이 어떻게든 코 마귀를 불러올 수 있다고 걱정한다면, 당신은 코 마귀를 교체함으로써 이것을 피할 수 있을 것이다.1
에[1]
구조물의 끝에 패딩이 없도록 하는 값.단순하지만 낭비적인 방법은 끝에 배열이 없는 경우를 제외하고 동일한 구성원으로 구조물을 만들어 사용하는 것이다.s[sizeof struct that_other_struct];
배열을 위해그러면.p->s[i]
구조물의 배열의 요소로 명확하게 정의된다.i<sizeof struct that_other_struct
Char 로서 그 의 끝 다음에 로.i>=sizeof struct that_other_struct
.
편집: 실제로 올바른 크기를 얻기 위한 위의 트릭에서 배열 자체가 일부 다른 요소의 패딩 중간이 아니라 최대 정렬로 시작되도록 배열 앞에 모든 단순 유형을 포함하는 결합을 넣어야 할 수도 있다.다시 말하지만, 나는 이 중 어떤 것도 필요하지 않다고 믿지만, 나는 언어 변호사들 중에서 가장 편집증적인 사람들을 위해 그것을 제안하고 있다.
편집 2: 표준의 다른 부분 때문에 패딩 바이트와의 중첩은 확실히 문제가 되지 않는다.C는 두 구조체가 초기 요소 분리에서 일치할 경우 공통 초기 요소들은 포인터를 통해 두 유형으로 접근할 수 있도록 요구한다.결과적으로, 만약 구조물이struct T
그러나 더 큰 최종 배열이 선언되었고, 원소는s[0]
원소와 일치해야 할 것이다.s[0]
에struct T
, 그리고 이러한 추가 요소의 존재는 다음과 같은 포인터를 사용하여 더 큰 구조물의 공통 요소에 접근함으로써 영향을 미치거나 영향을 받을 수 없다.struct T
.
컴파일러가 다음과 같은 것을 받아들일 경우
typef 구조 {in len;char dat[]; };
나는 'dat'의 길이를 넘어서서 첨자를 받아들일 준비가 되어 있어야 한다고 생각한다.반면에, 만약 누군가가 다음과 같은 것을 부호화한다면:
typef 구조 {무엇에든char dat[1]; }MY_STRUCT;
나중에 somestruct->dat[x]에 액세스한다.나는 컴파일러가 x의 큰 가치로 작동할 주소-컴퓨팅 코드를 사용해야 하는 어떠한 의무도 받고 있다고 생각하지 않는다. 만약 누군가가 정말로 안전하기를 원한다면, 적절한 패러다임은 다음과 더 비슷할 것이라고 생각한다.
#define MAZE_DAT_SIZE 0xF000typef 구조 {무엇에든char dat[]MOST_DAT_SIZE]; }MY_STRUCT;
그런 다음 (Sizeof(MYstRUT)-BASE_DAT_SIZE + wanted_array_length) 바이트의 malloc를 수행한다(원하는_array_length가 MAX_DAT_SIZE보다 클 경우 결과가 정의되지 않을 수 있음을 명심하십시오).
우연히도, 제로 길이 배열을 금지하기로 한 결정은 (터보 C와 같은 일부 오래된 방언은 더 큰 지수와 함께 작동할 코드를 컴파일러가 생성해야 한다는 신호로 간주될 수 있기 때문에 불행한 결정이었다고 생각한다.
참조URL: https://stackoverflow.com/questions/3711233/is-the-struct-hack-technically-undefined-behavior
'IT이야기' 카테고리의 다른 글
Vuejs 2가 어레이에서 이동하는 항목, 어레이 인덱싱 유지 안 함 (0) | 2022.04.28 |
---|---|
fflush(stdin) 사용 (0) | 2022.04.28 |
"정적 맥락에서 비정적 방법을 참조할 수 없다"는 이면의 이유는 무엇인가? (0) | 2022.04.28 |
@input 이벤트 실행 메서드 전에 값 프로펠러를 확인하시겠습니까?VeValidate/Vue (0) | 2022.04.28 |
vue-resource http 인터셉터 내부에서 사용자를 리디렉션하는 방법 (0) | 2022.04.28 |