Live data from Hacker News

*(char*)0 = 0; – What does the C++ programmer intend with this code? [video]

youtube.com

31–40 of 199 posts

Re: *(char*)0 = 0; – What does the C++ programmer intend with this code? [video]

#31
post #25

When I see that code my head immediately translates to "store a byte with value 0 at memory address zero". 0 the integer needs to be cast to a char * to treat it as a byte address (rather than a word) and the * means "assign to the memory location given by the pointer. I would probably use something like this when hacking on an Apple II with a zero page. I have also written programs that ran on VMS which dumped memor…

Exactly so [0]. However, these days something or someone somewhere might object "to keep us safe from ourselves"

Turbo-C used to check the zero location to see if it had changed and would issue a warning if so (in the real mode days).

[0] My other computer is a PDP-11,

Re: *(char*)0 = 0; – What does the C++ programmer intend with this code? [video]

#32
post #23

This statement would be technically legal on its own in x86 real mode if the compiler didn't do null pointer checks. However it would set the divide-by-zero IRQ handler to itself 0000:0000, and when the next division by zero happened, the machine run into UB (likely a reset or halt) because it would jump there, do 4x ADD byte ptr [BX + SI], AL (or ADD byte ptr [EAX], AL) followed by running the remaining interrupt ve…

If I'm understanding what you're saying correctly, the memory location with address 0 is actually a writable address, but with the value being used semantically to handle division by zero? It's kind of wild to me that would even something that's even allowed to be done manually, let alone required by a certain mode. Is this something provided for compatibility reasons that you'd have to opt into, or is it just someth…

Think of it as part of the “API” of the CPU that a program can make use of however it likes. In the early days (for DOS and the like), the distinction between operating system and application was more one of convention and not enforced by hardware mechanisms. The program was supposed to control the hardware, and not the other way around.

Re: *(char*)0 = 0; – What does the C++ programmer intend with this code? [video]

#34
post #11

Afaik, that's a "portable" way to trigger a termination with dump collection :) (particularly on Windows if you just std::terminate you won't get a heap dump unless you do special WER registration and register handlers).

Have used for a similar purpose as well (make sure a code path breaks).

Re: *(char*)0 = 0; – What does the C++ programmer intend with this code? [video]

#35
post #14

Earlier quoted context omitted.

Not quite. (char *) 0 is the null pointer. The null pointer is not necessarily a binary all-zero. On some compilers in x86, the null pointer intentionally points to something which will cause a crash when written to.

Find me one contemporary example (ANSI C) with a disassembled screenshot. This is writing sizeof(char) (== 1 almost everywhere) zero to address zero. It is not using a NULL macro or other predefined symbol. In the real world, this would generally write a byte to address 0000:0000, leading to UB because it would fuck up the divide-by-zero IV. PS: I used Borland C++ 3.1, Microsoft C++ 3.x and 4.5x, Watcom, and early GN…

> Find me one contemporary example (ANSI C) with a disassembled screenshot.

Here in godbolt, clang compiling C simply deletes the code in the function past and including the null pointer dereference.

https://godbolt.org/z/9aqWPazsP

> This is writing sizeof(char) (== 1 almost everywhere)

1 everywhere. sizeof's unit is "how many chars". For instance there was a cray machine that could only access 64bit words. sizeof(char) is still 1, with 64bit chars.

> zero to address zero. It is not using a NULL macro or other predefined symbol.

NULL is defined as literal 0.

Re: *(char*)0 = 0; – What does the C++ programmer intend with this code? [video]

#37

Old school comment... back in the day, there were computers on which dereferencing the null pointer would in fact return zero. And given that a typical linked list is terminated by a null, mistakes could creep in where "points to a 0" was checked when "pointer is 0" should have been. Needless to say, code like this would crash in mysterious ways when compiled on a machine where location 0, while readable, had nonzero…

I've actually seen code equivalent to this used for control flow. It was a very old bit of safety-critical code(!) that initialized a bunch of hardware from predefined tables containing pointers to overrides. For table values that weren't overridden (very few by the time I came along), it would access the null pointer to intentionally trap so an interrupt could insert the correct default and restart the instruction.

Certain higher ups determined that behavior was a business requirement when I refused to reimplement it on a new platform.

Re: *(char*)0 = 0; – What does the C++ programmer intend with this code? [video]

#38
post #14

Earlier quoted context omitted.

Not quite. (char *) 0 is the null pointer. The null pointer is not necessarily a binary all-zero. On some compilers in x86, the null pointer intentionally points to something which will cause a crash when written to.

Find me one contemporary example (ANSI C) with a disassembled screenshot. This is writing sizeof(char) (== 1 almost everywhere) zero to address zero. It is not using a NULL macro or other predefined symbol. In the real world, this would generally write a byte to address 0000:0000, leading to UB because it would fuck up the divide-by-zero IV. PS: I used Borland C++ 3.1, Microsoft C++ 3.x and 4.5x, Watcom, and early GN…

https://gcc.godbolt.org/z/hWEMnjT83

Re: *(char*)0 = 0; – What does the C++ programmer intend with this code? [video]

#39
post #23

This statement would be technically legal on its own in x86 real mode if the compiler didn't do null pointer checks. However it would set the divide-by-zero IRQ handler to itself 0000:0000, and when the next division by zero happened, the machine run into UB (likely a reset or halt) because it would jump there, do 4x ADD byte ptr [BX + SI], AL (or ADD byte ptr [EAX], AL) followed by running the remaining interrupt ve…

If I'm understanding what you're saying correctly, the memory location with address 0 is actually a writable address, but with the value being used semantically to handle division by zero? It's kind of wild to me that would even something that's even allowed to be done manually, let alone required by a certain mode. Is this something provided for compatibility reasons that you'd have to opt into, or is it just someth…

The interrupt vector table on x86 sits by default at 0000:0000 and the CPU uses it to handle interrupts and other exceptions by jumping to the address entry corresponding to the event. Entry 0 is division by 0, but there are also entries for illegal instruction, hardware interrupts and so on.

The address can be changed with the LIDT instruction and operating systems nowadays will just put it wherever, but for backward compatibility it is expected to still be at 0000:0000 (not sure how this is handled nowadays in UEFI, but it should still be possible t o set it up that way).

Re: *(char*)0 = 0; – What does the C++ programmer intend with this code? [video]

#40
post #23

This statement would be technically legal on its own in x86 real mode if the compiler didn't do null pointer checks. However it would set the divide-by-zero IRQ handler to itself 0000:0000, and when the next division by zero happened, the machine run into UB (likely a reset or halt) because it would jump there, do 4x ADD byte ptr [BX + SI], AL (or ADD byte ptr [EAX], AL) followed by running the remaining interrupt ve…

If I'm understanding what you're saying correctly, the memory location with address 0 is actually a writable address, but with the value being used semantically to handle division by zero? It's kind of wild to me that would even something that's even allowed to be done manually, let alone required by a certain mode. Is this something provided for compatibility reasons that you'd have to opt into, or is it just someth…

The kernel can write almost anywhere. (Well, actually, nothing can write on most addresses in a 64 bits machine, but if it's usable for something, the kernel can use it directly.)

And yes, some addresses are special. (AFAIK, on all current mainstream architectures.) This is the expected way to set those signal handlers, output (and input) data, configure devices, etc.

That said, there are some gotchas on using specific addresses in C. AFAIK none apply to x86, but it's something you usually do in assembly.

Post reply on HN