programing

주소 0에 액세스하고 싶을 수 있습니까?

copysource 2021. 1. 17. 11:49
반응형

주소 0에 액세스하고 싶을 수 있습니까?


상수 0은 C 및 C ++에서 널 포인터로 사용됩니다. 하지만 문제 같이 "포인터는 특정 고정 주소로 " 고정 된 주소를 할당의 몇 가지 가능한 사용에있을 것 같습니다. 어떤 시스템에서 어떤 하위 수준의 작업이든 주소 0에 액세스하기 위해 상상할 수있는 필요가 있습니까?

그렇다면 0이 널 포인터이고 모두 어떻게 해결됩니까?

그렇지 않다면 그러한 필요성이 없다는 것을 어떻게 확신합니까?


C 나 C ++에서 null 포인터 값은 어떤 식 으로든 물리적 주소에 연결되어 있지 않습니다 0. null 포인터 값에 대한 포인터를 설정하기 0 위해 소스 코드에서 상수를 사용한다는 사실은 단지 구문 적 설탕 조각에 지나지 않습니다 . 컴파일러는 특정 플랫폼에서 널 포인터 값으로 사용되는 실제 물리적 주소로 변환해야합니다.

즉, 0소스 코드에서 물리적 중요성이 전혀 없습니다. 예를 들어 42또는 13수 있습니다 . 즉, 언어 작성자가 마음에 든다면 p = 42포인터 p를 널 포인터 값 으로 설정하기 위해 수행해야 할 수 있도록 만들 수 있습니다 . 다시 말하지만, 이것은 물리적 주소 42가 널 포인터를 위해 예약되어야 한다는 것을 의미하지 않습니다 . 컴파일러는 소스 코드 p = 42를 실제 물리적 널 포인터 값 ( 0x0000또는 0xBAAD)을 포인터에 채우는 기계 코드 변환해야합니다 p. 그것이 지금 constant 0.

또한 C와 C ++는 포인터에 특정 물리적 주소를 할당 할 수있는 엄격하게 정의 된 기능을 제공하지 않습니다. 따라서 "포인터에 0 주소를 할당하는 방법"에 대한 귀하의 질문에는 공식적으로 답변이 없습니다. C / C ++에서는 포인터에 특정 주소를 할당 할 수 없습니다. 그러나 구현 정의 기능 영역에서 명시 적 정수 대 포인터 변환은 그 효과를 갖도록 의도되었습니다. 그래서 다음과 같이 할 것입니다.

uintptr_t address = 0;
void *p = (void *) address;

이것은 수행하는 것과 동일하지 않습니다.

void *p = 0;

후자는 항상 널 포인터 값을 생성하지만 전자는 일반적으로 생성하지 않습니다. 전자는 일반적으로 물리적 주소에 대한 포인터를 생성하며 0, 이는 주어진 플랫폼에서 널 포인터 값일 수도 있고 아닐 수도 있습니다.


접선 메모 : Microsoft의 C ++ 컴파일러를 사용하면 멤버에 대한 NULL 포인터가 32 비트 시스템에서 비트 패턴 0xFFFFFFFF로 표시된다는 사실에 관심이있을 수 있습니다. 그건:

struct foo
{
      int field;
};

int foo::*pmember = 0;     // 'null' member pointer

pmember는 'all ones'비트 패턴을 갖습니다. 이 값은 다음과 구별하기 위해 필요하기 때문입니다.

int foo::*pmember = &foo::field;

여기서 비트 패턴은 실제로 '모두 0'으로 표시됩니다. 오프셋 0을 구조 foo로 원하기 때문입니다.

다른 C ++ 컴파일러는 멤버에 대한 널 포인터에 대해 다른 비트 패턴을 선택할 수 있지만 중요한 점은 예상했던 모든 0 비트 패턴이 아니라는 것입니다.


당신은 잘못된 전제에서 시작하고 있습니다. 당신이 포인터로 값을 0으로 정수 상수를 할당 할 때, 그 된다 널 포인터 상수. 그러나 이것이 널 포인터가 반드시 주소 0을 참조한다는 것을 의미 하지는 않습니다 . 반대로, C 및 C ++ 표준은 널 포인터가 0이 아닌 일부 주소를 참조 할 수 있다는 점을 매우 명확합니다.

결론 은 이것이다 : 널 포인터가 참조 할 주소를 따로 설정 해야 하지만 본질적으로 선택한 모든 주소가 될 수 있습니다. 0을 포인터로 변환 할 때 선택한 주소를 참조해야합니다. 그러나 이것이 실제로 필요한 전부입니다. 예를 들어, 정수를 포인트로 변환하는 것이 정수에 0x8000을 더하는 것을 의미한다고 결정한 경우, 널 포인터는 실제로 주소 0 대신 주소 0x8000을 참조합니다.

null 포인터를 역 참조하면 정의되지 않은 동작이 발생한다는 점도 주목할 가치가 있습니다. 즉, 당신은 그것을 할 수없는 휴대용 코드,하지만 않습니다 하지 당신은 전혀 그것을 할 수없는 것은. 작은 마이크로 컨트롤러 등을위한 코드를 작성할 때 전혀 이식 할 수없는 일부 코드를 포함하는 것이 일반적입니다. 한 주소에서 읽기는 일부 센서의 값을 제공 할 수 있지만 동일한 주소에 쓰기는 스테퍼 모터를 활성화 할 수 있습니다 (예 : 다음 장치 (정확히 동일한 프로세서를 사용하더라도)가 연결될 수 있으므로 두 주소 모두 대신 일반 RAM을 참조합니다.

널 포인터가더라도 않습니다 그냥 그렇게하지 못하도록 - 주소 0을 참조하는 읽기 및 / 또는 그 주소에 될 일이 무엇이든 기록하는 데 사용하지 못하도록하지 않습니다 이식 -하지만하지 않습니다 정말 중요합니다. 주소 0이 일반적으로 중요한 유일한 이유는 일반 저장소가 아닌 다른 것에 연결하기 위해 디코딩 된 경우이므로 어쨌든 완전히 이식 가능하게 사용할 수 없습니다.


컴파일러가이를 처리합니다 ( comp.lang.c FAQ ).

기계가 널 포인터에 0이 아닌 비트 패턴을 사용하는 경우, 프로그래머가 널 포인터 인 "0"또는 "NULL"을 작성하여 요청할 때이를 생성하는 것은 컴파일러의 책임입니다. 따라서 내부 널 포인터가 0이 아닌 머신에서 NULL을 0으로 정의하는 것은 다른 것만 큼 유효합니다. 왜냐하면 컴파일러는 포인터 컨텍스트에서 표시되는 장식되지 않은 0에 대한 응답으로 머신의 올바른 널 포인터를 생성해야하고 생성 할 수 있기 때문입니다.

포인터가 아닌 컨텍스트에서 0을 참조하여 주소 0을 얻을 수 있습니다.


실제로 C 컴파일러는 프로그램이 주소 0에 쓰기를 시도하도록 기꺼이 허용합니다. 런타임에 NULL 포인터에 대해 모든 포인터 작업을 확인하는 것은 비용이 많이 듭니다. 컴퓨터에서는 운영 체제가 금지하기 때문에 프로그램이 충돌합니다. 메모리 보호 기능이없는 임베디드 시스템에서 프로그램은 실제로 주소 0에 기록하여 종종 전체 시스템을 충돌시킵니다.

주소 0은 임베디드 시스템에서 유용 할 수 있습니다 (컴퓨터에없는 CPU의 일반적인 용어, 스테레오에서 디지털 카메라까지 모든 것을 실행합니다). 일반적으로 시스템은 주소 0에 쓸 필요가 없도록 설계되어 있습니다. 제가 아는 모든 경우에는 일종의 특수 주소입니다. 프로그래머가 (예를 들어 인터럽트 테이블을 설정하기 위해) 쓰기를해야 할지라도, 그들은 초기 부팅 시퀀스 (보통 C에 대한 환경을 설정하기위한 짧은 어셈블리 언어) 동안에 만 쓰기를하면됩니다.


메모리 주소 0은 Zero Page 라고도합니다 . 이것은 BIOS에 의해 채워지며 시스템에서 실행되는 하드웨어에 대한 정보를 포함합니다. 모든 최신 커널은이 메모리 영역을 보호합니다. 이 메모리에 액세스 할 필요는 없지만 커널 영역 내에서 액세스해야하는 경우 커널 모듈이 트릭을 수행합니다.


x86에서 주소 0 (또는 오히려 0000 : 0000)과 리얼 모드에서 그 부근은 인터럽트 벡터의 위치입니다. 예전에는 일반적으로 인터럽트 벡터에 값을 작성하여 인터럽트 처리기를 설치했습니다 (또는 더 잘 훈련 된 경우 MS-DOS 서비스 0x25 사용). MS-DOS 용 C 컴파일러는 NULL 또는 0이 할당 될 때 세그먼트 부분에서 비트 패턴 0000을 수신하고 오프셋 부분에서 0000을 수신하는 먼 포인터 유형을 정의했습니다.

물론 값이 0000 : 0000 인 원거리 포인터에 실수로 쓴 잘못된 프로그램은 시스템에서 매우 나쁜 일이 발생하여 일반적으로 시스템을 잠그고 강제로 재부팅합니다.


링크의 질문에서 사람들은 마이크로 컨트롤러의 고정 주소 설정에 대해 논의하고 있습니다 . 마이크로 컨트롤러를 프로그래밍하면 모든 것이 훨씬 낮은 수준에 있습니다.

데스크톱 / 서버 PC 측면에서 OS도없고 가상 메모리와 같은 것도 없습니다. 따라서 특정 주소의 메모리에 액세스하는 것이 좋습니다. 최신 데스크톱 / 서버 PC에서는 쓸모없고 심지어 위험합니다.


MMU가없고 0이 완벽하게 좋은 주소 인 Motorola HC11 용 gcc를 사용하여 일부 코드를 컴파일했습니다. 주소 0에 쓰려면 그냥 쓰기 만하면된다는 사실에 실망했습니다. NULL과 주소 0 사이에는 차이가 없습니다.

그리고 그 이유를 알 수 있습니다. 내 말은, 모든 메모리 위치가 잠재적으로 유효한 아키텍처에서 고유 한 NULL을 정의하는 것은 실제로 불가능하므로 gcc 작성자는 0이 유효한 주소인지 여부에 관계없이 NULL에 충분하다고 말했습니다.

      char *null = 0;
; Clears 8-bit AR and BR and stores it as a 16-bit pointer on the stack.
; The stack pointer, ironically, is stored at address 0.
1b:   4f              clra
1c:   5f              clrb
1d:   de 00           ldx     *0 <main>
1f:   ed 05           std     5,x

다른 포인터와 비교할 때 컴파일러는 일반 비교를 생성합니다. 이것은 char *null = 0특별한 NULL 포인터로 간주되지 않으며 실제로 주소 0에 대한 포인터와 "NULL"포인터가 동일하다는 것을 의미합니다.

; addr is a pointer stored at 7,x (offset of 7 from the address in XR) and 
; the "NULL" pointer is at 5,y (offset of 5 from the address in YR).  It doesn't
; treat the so-called NULL pointer as a special pointer, which is not standards
; compliant as far as I know.
37:   de 00           ldx     *0 <main>
39:   ec 07           ldd     7,x
3b:   18 de 00        ldy     *0 <main>
3e:   cd a3 05        cpd     5,y
41:   26 10           bne     53 <.LM7>

따라서 원래 질문을 해결하기 위해 내 대답은 컴파일러 구현을 확인하고 고유 값 NULL을 구현하는 데 방해가되는지 확인하는 것입니다. 그렇지 않다면 걱정할 필요가 없습니다. ;)

(물론이 답변은 표준을 준수하지 않습니다.)


그것은 모두 머신에 가상 메모리가 있는지 여부에 달려 있습니다. 이 기능이있는 시스템은 일반적으로 쓰기 불가능한 페이지를 거기에 배치하는데, 이는 아마도 익숙한 동작 일 것입니다. 그러나 그것이없는 시스템 (일반적으로 요즘 마이크로 컨트롤러이지만 훨씬 더 일반적이었습니다)에서는 종종 인터럽트 테이블과 같은 영역에서 매우 흥미로운 것들이 있습니다. 나는 8 비트 시스템 시절에 이런 것들을 해킹했던 기억이 난다. 시스템을 하드 리셋하고 다시 시작해야 할 때 너무 큰 고통은 아닙니다 . :-)


예, 메모리 주소 0x0h에 액세스 할 수 있습니다. 이를 수행하려는 이유 는 플랫폼에 따라 다릅니다. 프로세서는 이것을 리셋 벡터에 사용할 수 있으며, 이에 쓰면 CPU가 리셋됩니다. 또한 일부 하드웨어 리소스 (프로그램 카운터, 시스템 클럭 등)에 대한 메모리 매핑 인터페이스로 인터럽트 벡터에 사용되거나 일반 이전 메모리 주소로 유효 할 수도 있습니다. 메모리 주소 0에 대해 반드시 마법 같은 것은 없습니다. 이것은 역사적으로 특별한 목적 (벡터 재설정 등)으로 사용 된 것입니다. C와 유사한 언어는 NULL 포인터의 주소로 0을 사용하여 이러한 전통을 따르지만 실제로 기본 하드웨어는 주소 0을 특별하게 인식하거나 인식하지 않을 수 있습니다.

The need to access address zero usually arises only in low-level details like bootloaders or drivers. In these cases, the compiler can provide options/pragmas to compile a section of code without optimizations (to prevent the zero pointer from being extracted away as a NULL pointer) or inline assembly can be used to access the true address zero.


C/C++ don't allows you to write to any address. It is the OS that can raise a signal when a user access some forbidden address. C and C++ ensure you that any memory obtained from the heap, will be different of 0.


I have at times used loads from address zero (on a known platform where that would be guaranteed to segfault) to deliberately crash at an informatively named symbol in library code if the user violates some necessary condition and there isn't any good way to throw an exception available to me. "Segfault at someFunction$xWasnt16ByteAligned" is a pretty effective error message to alert someone to what they did wrong and how to fix it. That said, I wouldn't recommend making a habit of that sort of thing.


Writing to address zero can be done, but it depends upon several factors such as your OS, target architecture and MMU configuration. In fact, it can be a useful debugging tool (but not always).

For example, a few years ago while working on an embedded system (with few debugging tools available), we had a problem which was resulting in a warm reboot. To help locate the problem, we were debugging using sprintf(NULL, ...); and a 9600 baud serial cable. As I said--few debugging tools available. With our setup, we knew that a warm reboot would not corrupt the first 256 bytes of memory. Thus after the warm reboot we could pause the loader and dump the memory contents to find out what happened prior to reboot.


Remember that in all normal cases, you don't actually see specific addresses. When you allocate memory, the OS supplies you with the address of that chunk of memory.

When you take the reference of a variable, the the variable has already been allocated at an address determined by the system.

So accessing address zero is not really a problem, because when you follow a pointer, you don't care what address it points to, only that it is valid:

int* i = new int(); // suppose this returns a pointer to address zero
*i = 42; // now we're accessing address zero, writing the value 42 to it

So if you need to access address zero, it'll generally work just fine.

The 0 == null thing only really becomes an issue if for some reason you're accessing physical memory directly. Perhaps you're writing an OS kernel or something like that yourself. In that case, you're going to be writing to specific memory addresses (especially those mapped to hardware registers), and so you might conceivably need to write to address zero. But then you're really bypassing C++ and relying on the specifics of your compiler and hardware platform.

Of course, if you need to write to address zero, that is possible. Only the constant 0 represents a null pointer. The non-constant integer value zero will not, if assigned to a pointer, yield a null pointer.

So you could simply do something like this:

int i = 0;
int* zeroaddr = (int*)i;

now zeroaddr will point to address zero(*), but it will not, strictly speaking, be a null pointer, because the zero value was not constant.

(*): that's not entirely true. The C++ standard only guarantees an "implementation-defined mapping" between integers and addresses. It could convert the 0 to address 0x1633de20` or any other address it likes. But the mapping is usually the intuitive and obvious one, where the integer 0 is mapped to the address zero)


If I remember correctly, in an AVR microcontroller the register file is mapped into an address space of RAM and register R0 is at the address 0x00. It was clearly done in purpose and apparently Atmel thinks there are situations, when it's convenient to access address 0x00 instead of writing R0 explicitly.

In the program memory, at the address 0x0000 there is a reset interrupt vector and again this address is clearly intended to be accessed when programming the chip.


It may surprise many people, but in the core C language there is no such thing as a special null pointer. You are totally free to read and write to address 0 if it's physically possible.

The code below does not even compile, as NULL is not defined:

int main(int argc, char *argv[])
{
    void *p = NULL;
    return 0;
}

OTOH, the code below compiles, and you can read and write address 0, if the hardware/OS allows:

int main(int argc, char *argv[])
{
    int *p = 0;
    *p = 42;
    int x = *p; /* let's assume C99 */
}

Please note, I did not include anything in the above examples. If we start including stuff from the standard C library, NULL becomes magically defined. As far as I remember it comes from string.h.

NULL is still not a core C feature, it's a CONVENTION of many C library functions to indicate the invalidity of pointers. The C library on the given platform will define NULL to a memory location which is not accessible anyway. Let's try it on a Linux PC:

#include <stdio.h>
int main(int argc, char *argv[])
{
        int *p = NULL;
        printf("NULL is address %p\n", p);
        printf("Contents of address NULL is %d\n", *p);
        return 0;
}

The result is:

NULL is address 0x0
Segmentation fault (core dumped)

So our C library defines NULL to address zero, which it turns out is inaccessible. But it was not the C compiler, of not even the C-library function printf() that handled the zero address specially. They all happily tried to work with it normally. It was the OS that detected a segmentation fault, when printf tried to read from address zero.

ReferenceURL : https://stackoverflow.com/questions/2761360/could-i-ever-want-to-access-the-address-zero

반응형