Programing

& ((struct name *) NULL-> b)는 C11에서 정의되지 않은 동작을 유발합니까?

lottogame 2020. 12. 26. 09:26
반응형

& ((struct name *) NULL-> b)는 C11에서 정의되지 않은 동작을 유발합니까?


코드 샘플 :

struct name
{
    int a, b;
};

int main()
{
    &(((struct name *)NULL)->b);
}

이로 인해 정의되지 않은 동작이 발생합니까? 우리는 그것이 "무역 참조"인지에 대해 토론 할 수 있지만, C11은 "역 참조"라는 용어를 정의하지 않습니다.

6.5.3.2/4 *는 널 포인터를 사용하면 정의되지 않은 동작이 발생 한다고 명확하게 말합니다 . 그러나 그것은 동일한 것을 말하지 않으며 ->또한 a -> b존재로 정의하지 않습니다 (*a).b; 각 연산자에 대해 별도의 정의가 있습니다.

->6.5.2.3/4 의 의미는 다음 과 같습니다.

-> 연산자와 식별자가 뒤 따르는 접미사 식은 구조체 또는 공용체 개체의 구성원을 지정합니다. 값은 첫 번째 표현식이 가리키는 객체의 명명 된 멤버의 값이며 lvalue입니다.

그러나 NULL객체를 가리 키지 않기 때문에 두 번째 문장이 제대로 지정되지 않은 것 같습니다.

6.5.3.2/1도 관련이있을 수 있습니다.

제약 :

단항 &연산자 의 피연산자는 함수 지정자, []또는 단항 *연산자 의 결과 이거나 , 비트 필드가 아니고 레지스터 스토리지 클래스 지정자로 선언되지 않은 객체지정하는 lvalue 이어야 합니다.

그러나 굵게 표시된 텍스트는 결함이 있으며 6.3.2.1/1 ( lvalue 정의)에 따라 잠재적으로 객체를 지정하는 lvalue를 읽어야한다고 생각합니다 -C99가 lvalue 의 정의를 엉망으로 만들었 기 때문에 C11이 다시 작성해야했습니다. 섹션이 누락되었습니다.

6.3.2.1/1은 다음과 같이 말합니다.

lvalue는 객체를 잠재적으로 지정하는 표현식 (void 이외의 객체 유형)입니다. lvalue가 평가 될 때 객체를 지정하지 않으면 동작이 정의되지 않습니다.

그러나 &연산자 피연산자를 평가합니다. (저장된 값에 액세스하지 않지만 다릅니다).

이 긴 추론 체인은 코드가 UB를 유발한다는 것을 암시하는 것처럼 보이지만 상당히 약하고 표준 작성자가 의도 한 바가 명확하지 않습니다. 사실 그들은 토론하도록 우리에게 맡기지 않고 의도 한 것이 있다면 :)


변호사의 관점에서 &(((struct name *)NULL)->b);UB가없는 길을 찾을 수 없기 때문에 표현 은 UB로 이어져야합니다. IMHO의 근본 원인은 현재 ->객체를 가리 키지 않는 표현식에 연산자 를 적용 하는 것입니다.

컴파일러 프로그래머가 overcomplicated되지 않은 가정보기의 컴파일러 점에서, 표현이 같은 값 반환 분명하다 offsetof(name, b)겠습니까을, 나는 꽤 있는지 해요 오류없이 컴파일 제공하는 기존의 컴파일러가 그 결과를 제공 할 것입니다.

쓰여진 바와 같이, 우리는 내부 부분 ->에서 표현식에 연산자를 사용 하는 것보다 객체를 가리킬 수없고 (널이기 때문에) 경고 나 오류를 발행 할 수 없다는 컴파일러를 비난 할 수 없습니다.

내 결론은 주소를 취하는 것이 합법적이라는 특별한 단락이있을 때까지 널 포인터를 역 참조하는 것이 합법적이라는 것입니다.이 표현은 합법적 인 C가 아닙니다.


예,이 사용 ->은 정의되지 않은 영어 용어의 직접적인 의미에서 정의되지 않은 동작 가지고 있습니다.

동작은 첫 번째 표현식이 객체를 가리키는 경우에만 정의되고 그렇지 않으면 정의되지 않습니다 (= 정의되지 않음). 일반적으로 정의되지 않은 용어로 더 많이 검색해서는 안됩니다. 이는 표준이 코드에 대한 의미를 제공하지 않는다는 의미입니다. (때로는 정의하지 않는 상황을 명시 적으로 가리 키지 만 용어의 일반적인 의미는 변경되지 않습니다.)

이것은 컴파일러 빌더가 작업을 처리하는 데 도움이되도록 도입 된 느슨 함입니다. 그들은 당신이 제시하는 코드에 대해서도 행동을 정의 할 수 있습니다. 특히 컴파일러 구현의 경우 이러한 코드 또는 유사한 offsetof매크로 를 사용하는 것이 좋습니다. 이 코드를 제약 조건 위반으로 만들면 컴파일러 구현을 위해 해당 경로가 차단됩니다.


간접 연산자부터 시작하겠습니다 *.

6.5.3.2 p4 : 단항 * 연산자는 간접을 나타냅니다. 피연산자가 함수를 가리키는 경우 결과는 함수 지정자입니다. 객체를 가리키는 경우 결과는 객체를 지정하는 lvalue입니다. 피연산자의 유형이 "입력 할 포인터"인 경우 결과 유형은 "유형"입니다. 포인터에 잘못된 값이 할당 된 경우 단항 *연산자 의 동작 은 정의되지 않습니다. 102)

E가 널 포인터 인 * E는 정의되지 않은 동작입니다.

다음과 같은 각주가 있습니다.

102) 따라서 &*E는 E (E가 널 포인터 인 경우에도) 및 & (E1 [E2]) ~ ((E1) + (E2))와 동일합니다. E가 함수 지정자이거나 단항 & 연산자의 유효한 피연산자 인 lvalue이면 * & E는 함수 지정자이거나 E와 같은 lvalue입니다. * P가 lvalue이고 T가 다음의 이름 인 경우는 항상 참입니다. 객체 포인터 유형, * (T) P는 T가 가리키는 것과 호환되는 유형을 가진 lvalue입니다.

즉, E가 NULL 인 & * E가 정의되어 있지만 질문은 & (* E) .m에 대해서도 동일한 지 여부입니다. 여기서 E는 널 포인터이고 해당 유형은 멤버 m이있는 구조체입니다. ?

C Standard는 그 행동을 정의하지 않습니다.

정의되면 새로운 문제가 발생하며 그중 하나가 아래에 나열됩니다. C Standard는 정의되지 않은 상태로 유지하는 것이 정확하며 내부적으로 문제를 처리하는 매크로 오프셋을 제공합니다.

6.3.2.3 포인터

  1. 값이 0 인 정수 상수 표현식 또는 void * 유형으로 캐스트 된 이러한 표현식을 널 포인터 상수라고합니다. 66) 널 포인터 상수가 포인터 유형으로 변환되면 널 포인터라고하는 결과 포인터는 객체 또는 함수에 대한 포인터와 같지 않은 비교를 보장합니다.

이는 값이 0 인 정수 상수 표현식이 널 포인터 상수로 변환됨을 의미합니다.

그러나 널 포인터 상수의 값은 0으로 정의되지 않습니다. 값은 구현 정의입니다.

7.19 공통 정의

  1. 매크로는 구현 정의 널 포인터 상수로 확장되는 NULL입니다.

즉, C는 null 포인터가 모든 비트가 설정된 값을 가지며 해당 값에 대한 멤버 액세스를 사용하면 정의되지 않은 동작 인 오버플로가 발생하는 구현을 허용합니다.

또 다른 문제는 & (* E) .m을 어떻게 평가합니까? 대괄호가 적용되고 *먼저 평가됩니다. 정의되지 않은 상태로 유지하면이 문제가 해결됩니다.


먼저, 객체에 대한 포인터가 필요하다는 것을 설정합시다.

6.5.2.3 구조 및 조합원

4 ->연산자와 식별자가 뒤 따르는 접미사 식은 구조체 또는 공용체 개체의 구성원을 지정합니다 . 값은 첫 번째식이 가리키는 개체의 명명 된 멤버의 값이며 lvalue입니다 .96) 첫 번째식이 정규화 된 형식에 대한 포인터 인 경우 결과는 정규화 된 형식의 형식을 갖습니다. 지정된 회원.

불행히도 null 포인터 는 개체를 가리 키지 않습니다 .

6.3.2.3 포인터

3 An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.66) If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

Result: Undefined Behavior.

As a side-note, some other things to chew over:

6.3.2.3 Pointers

4 Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal.
5 An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.67)
6 Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

67) The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.

So even if the UB should happen to be benign this time, it might still result in some totally unexpected number.


No. Let's take this apart:

&(((struct name *)NULL)->b);

is the same as:

struct name * ptr = NULL;
&(ptr->b);

The first line is obviously valid and well defined.

In the second line, we calculate the address of a field relative to the address 0x0 which is perfectly legal as well. The Amiga, for example, had the pointer to the kernel in the address 0x4. So you could use a method like this to call kernel functions.

In fact, the same approach is used on the C macro offsetof (wikipedia):

#define offsetof(st, m) ((size_t)(&((st *)0)->m))

So the confusion here revolves around the fact that NULL pointers are scary. But from a compiler and standard point of view, the expression is legal in C (C++ is a different beast since you can overload the & operator).


Nothing in the C standard would impose any requirements on what a system could do with the expression. It would, when the standard was written, have been perfectly reasonable for it to to cause the following sequence of events at runtime:

  1. Code loads a null pointer into the addressing unit
  2. Code asks the addressing unit to add the offset of field b.
  3. The addressing unit trigger a trap when attempting to add an integer to a null pointer (which should for robustness be a run-time trap, even though many systems don't catch it)
  4. The system starts executing essentially random code after being dispatched through a trap vector that was never set because code to set it would have wasted been a waste of memory, as addressing traps shouldn't occur.

The very essence of what Undefined Behavior meant at the time.

Note that most of the compilers that have appeared since the early days of C would regard the address of a member of an object located at a constant address as being a compile-time constant, but I don't think such behavior was mandated then, nor has anything been added to the standard which would mandate that compile-time address calculations involving null pointers be defined in cases where run-time calculations would not.

ReferenceURL : https://stackoverflow.com/questions/26906621/does-struct-name-null-b-cause-undefined-behaviour-in-c11

반응형