NULL 재정의
주소 0x0000이 유효하고 포트 I / O를 포함하는 시스템의 C 코드를 작성 중입니다. 따라서 NULL 포인터에 액세스 할 수있는 모든 버그는 감지되지 않은 상태로 유지되며 동시에 위험한 동작을 유발합니다.
이러한 이유로 NULL을 다른 주소, 예를 들어 유효하지 않은 주소로 재정의하고 싶습니다. 실수로 그러한 주소에 액세스하면 오류를 처리 할 수있는 하드웨어 인터럽트가 발생합니다. 이 컴파일러의 stddef.h에 액세스 할 수 있으므로 실제로 표준 헤더를 변경하고 NULL을 재정의 할 수 있습니다.
내 질문은 : 이것이 C 표준과 충돌합니까? 표준에서 7.17부터 알 수있는 한 매크로는 구현 정의되어 있습니다. NULL에 0이 있어야 한다는 표준의 다른 부분이 있습니까?
또 다른 문제는 많은 컴파일러가 데이터 유형에 관계없이 모든 것을 0으로 설정하여 정적 초기화를 수행한다는 것입니다. 표준에 따르면 컴파일러는 정수를 0으로 설정하고 포인터를 NULL로 설정해야한다고 말합니다. 컴파일러에 NULL을 다시 정의하면 이러한 정적 초기화가 실패한다는 것을 알고 있습니다. 컴파일러 헤더를 수동으로 대담하게 변경했지만 잘못된 컴파일러 동작으로 간주 할 수 있습니까? 정적 초기화를 수행 할 때이 특정 컴파일러가 NULL 매크로에 액세스하지 않는다는 것을 알고 있기 때문에.
C 표준에서는 머신의 주소 0에 널 포인터가 필요하지 않습니다. 그러나 0
포인터 값 으로 상수를 캐스팅하면 NULL
포인터 (§6.3.2.3 / 3)가되어야하며 널 포인터를 부울로 평가하는 것은 거짓이어야합니다. 이것은 당신이 정말로 경우 조금 어색 할 수 있습니다 않는 제로 주소를 원하고, NULL
제로 주소가 아닙니다.
그럼에도 불구하고 컴파일러와 표준 라이브러리를 (무거운) 수정하여 표준 라이브러리를 NULL
엄격하게 준수하면서 대체 비트 패턴으로 표현할 수는 없습니다 . 그러나 그 자체로 정의를 변경하는 것만으로 는 충분 하지 않으며, 그럴 경우 true로 평가됩니다.NULL
NULL
특히 다음을 수행해야합니다.
- 포인터에 대한 할당 (또는 포인터에 캐스트)에서 리터럴 0이와 같은 다른 마술 값으로 변환되도록 정렬합니다
-1
. 0
대신 매직 값을 확인하기 위해 포인터와 상수 정수 사이의 동등성 테스트를 준비하십시오 (§6.5.9 / 6)- 포인터 유형이 부울로 평가되는 모든 컨텍스트에 대해 0을 확인하는 대신 마법 값과 동일한 지 확인하십시오. 이것은 평등 테스트 시맨틱에서 비롯되지만 컴파일러는 내부적으로 다르게 구현할 수 있습니다. §6.5.13 / 3, §6.5.14 / 3, §6.5.15 / 4, §6.5.3.3 / 5, §6.8.4.1 / 2, §6.8.5 / 4 참조
- caf가 지적했듯이 새로운 null 포인터 표현을 반영하도록 정적 객체 (§6.7.8 / 10) 및 부분 복합 이니셜 라이저 (§6.7.8 / 21)의 초기화에 대한 의미를 업데이트하십시오.
- 실제 주소 0에 액세스하는 다른 방법을 작성하십시오.
처리 할 필요가 없는 것들이 있습니다 . 예를 들면 다음과 같습니다.
int x = 0;
void *p = (void*)x;
그 후에 p
는 널 포인터가 보장되지 않습니다. 상수 할당 만 처리하면됩니다 (실제 주소 0에 액세스하기위한 좋은 방법입니다). 마찬가지로:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
또한:
void *p = NULL;
int x = (int)p;
x
보장되지 않습니다 0
.
요컨대,이 조건은 C 언어위원회에 의해 명백히 고려되었으며 NULL에 대한 대체 표현을 선택하려는 사람들을 위해 고려되었습니다. 컴파일러에서 주요 변경을 수행하기 만하면됩니다.
참고로, 컴파일러가 제대로 작동하기 전에 소스 코드 변환 단계를 통해 이러한 변경 사항을 구현할 수 있습니다. 즉, 일반적인 전 처리기-> 컴파일러-> 어셈블러-> 링커 대신 전 처리기-> NULL 변환-> 컴파일러-> 어셈블러-> 링커를 추가합니다. 그러면 다음과 같은 변환을 수행 할 수 있습니다.
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
이것은 포인터에 해당하는 식별자를 결정하기 위해 타입 파서와 타입 정의 및 변수 선언의 분석뿐만 아니라 완전한 C 파서가 필요합니다. 그러나 이렇게하면 컴파일러의 코드 생성 부분을 적절하게 변경하지 않아도됩니다. clang 은 이것을 구현하는 데 유용 할 수 있습니다-나는 이것이 이와 같은 변형을 염두에두고 설계 되었음을 이해합니다. 물론 표준 라이브러리를 변경해야 할 수도 있습니다.
표준에 따르면 값이 0 인 정수 상수 표현식 또는 void *
유형으로 변환 된 표현식 은 널 포인터 상수입니다. 이것은 (void *)0
항상 널 포인터이지만 주어진다 int i = 0;
는 (void *)i
것은 아닙니다.
C 구현은 헤더와 함께 컴파일러로 구성됩니다. 헤더를 redefine으로 NULL
수정하지만 정적 초기화를 수정하도록 컴파일러를 수정하지 않으면 부적합한 구현을 작성한 것입니다. 잘못된 행동을 취하는 것은 전체 구현으로 이루어지며, 파산 한 사람은 아무도 비난 할 사람이 없습니다.)
포인터를 부여 - 당신은 물론, 정적 인 initialisations보다 더 수정해야합니다 p
, if (p)
에 해당 if (p != NULL)
인해 위의 규칙.
C std 라이브러리를 사용하면 NULL을 반환 할 수있는 함수에 문제가 발생합니다. 예를 들어 malloc 설명서 는 다음과 같이 말합니다.
함수가 요청 된 메모리 블록을 할당하지 못한 경우 널 포인터가 리턴됩니다.
malloc 및 관련 함수는 특정 NULL 값을 가진 바이너리로 이미 컴파일되어 있으므로 NULL을 재정의하면 C std 라이브러리를 포함하여 전체 툴 체인을 다시 빌드 할 수 없으면 C std 라이브러리를 직접 사용할 수 없습니다.
또한 std 라이브러리의 NULL 사용으로 인해 std 헤더를 포함하기 전에 NULL을 재정의하면 헤더에 나열된 NULL 정의를 덮어 쓸 수 있습니다. 인라인 된 것은 컴파일 된 객체와 일치하지 않습니다.
대신 자신의 사용을 위해 자신의 NULL "MYPRODUCT_NULL"을 정의하고 C 표준 라이브러리를 피하거나 변환합니다.
NULL 만 남겨두고 IO를 포트 0x0000에 특수한 경우로 처리합니다. 어쩌면 어셈블러로 작성된 루틴을 사용하기 때문에 표준 C 의미론이 적용되지 않습니다. IOW, NULL을 재정의하지 말고 포트 0x00000을 재정의하십시오.
Note that if you're writing or modifying a C compiler, the work required to avoid dereferencing NULL (assuming that in your case the CPU isn't helping) is the same no matter how NULL is defined, so it's easier to leave NULL defined as zero, and make sure that zero can't ever be dereferenced from C.
Considering the extreme difficulty in redefining NULL as mentioned by others, maybe its easier to redefine dereferencing for well-known hardware addresses. When creating an address, add 1 to every well-known address, so that your well-known IO port would be:
#define CREATE_HW_ADDR(x)(x+1)
#define DEREFERENCE_HW_ADDR(x)(*(x-1))
int* wellKnownIoPort = CREATE_HW_ADDR(0x00000000);
printf("IoPortIs" DEREFERENCE_HW_ADDR(wellKnownIoPort));
If the addresses you're concerned with are grouped together and you can feel safe that adding 1 to the address won't conflict with anything (which it shouldn't in most cases), you might be able to do this safely. And then you don't need to worry about rebuilding your tool chain/std lib and expressions in the form:
if (pointer)
{
...
}
still work
Crazy I know, but just thought I'd throw the idea out there :).
The bit pattern for the null pointer may not be the same as the bit pattern for the integer 0. But the expansion of the NULL macro must be a null pointer constant, that is a constant integer of value 0 which may be casted to (void*).
To achieve the result you want while staying conformant, you'll have to modify (or perhaps configurate) your tool chain, but it is achievable.
You're asking for trouble. Redefining NULL
to a non-null value will break this code:
if (myPointer) { // myPointer is not null ... }
참고URL : https://stackoverflow.com/questions/5142251/redefining-null
'Programming' 카테고리의 다른 글
SQL-다 대다 테이블 기본 키 (0) | 2020.07.17 |
---|---|
Kubernetes는 실제로 무엇을합니까? (0) | 2020.07.17 |
도커 호스트 OS와 컨테이너 기본 이미지 OS의 관계는 무엇입니까? (0) | 2020.07.17 |
: 후 vs. :: 후 (0) | 2020.07.17 |
c ++ 11 람다는 사용하지 않는 변수를 캡처합니까? (0) | 2020.07.17 |