Live data from Hacker News

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

youtube.com

131–140 of 199 posts

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

#131
post #91
post #76

Earlier quoted context omitted.

> sizeof char is 1 by definition everywhere. Parentheses are required around char because it's a type. /pedantic

That is incorrect :-). sizeof is an operator in C, and does not need parenthesis any more than pointer operator *. It is true that programmers frequently think of it as a function and use parenthesis.

That's incorrect, from GCC: error: expected parentheses around type name in sizeof expression.

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

#132
post #123
post #55

Earlier quoted context omitted.

I'd agree with that, to the extent that understanding how badly your tools can hurt you is an important thing to learn. Consider how a surgeon would respond if told not to use a scalpel because of the risk of accidental injury when using a sharp tool. We learn from our mistakes - to which I'd add, sometimes we can afford to make mistakes (home programs) and other times we can't (safety-critical code).

That surgeon has undergone several years training, had to go through an exam to be allowed to practice, and is submited to yearly evaluations if they are still allowed to touch that scapel. I would agree the same for developers, if similar practices would be enforced everywhere instead of having people calling themselves enginners just because they like how the word sounds.

Yet surgeons killed people all of the time and had to be forced to use checklists because it turns out credentialism doesn’t prevent mistakes.

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

#135

I once used a (never executed) *(char*)0=0; to force GCC to generate async unwind tables for an otherwise leaf function that could throw from an inline asm statement. It was just an experiment never meant for production, but it was quite effective at doing exactly what I needed.

That sounds very interesting but i don't quite understand :-( What were you trying to do and how did this work? I am interested in understanding the interplay between -funwind-tables, -fasyncronous-unwind-tables and -fexceptions.

To correctly handle exceptions GCC generates DWARF unwind tables that the runtime uses during unwinding to call the correct destructors but also restore registers and fixup the stack (i.e. the compensation code). Normally unwind tables have entries for each non-noexcept function call in a function as these are the only exception throwing edges. Asynchronous unwind also generates table entries for any instruction that can trap (typically memory accesses, but also things like division, etc). These can be used for example to correctly handle exceptions thrown by signal handlers, which is needed for ADA and Java support.

I had an inline asm statement that could throw exceptions (by indirectly calling exception throwing functions). GCC assumes that asm cannot throw, and the function the statement was in did not have any throw statement nor call any other potentially throwing functions nor any non-optimized out memory access, so the compiler omitted generating unwind tables for the function even with -fasync-unwind-tables.

The result was that throwing from the asm would at best not call destructors, at worse, crash with a corrupted stack.

By adding a non-optimizable dummy memory access right after the asm (and jumping over it from the asm, so it actually doesn't get executed), I forced GCC to generate the unwind info . I also had to make sure that no compensation code was needed for the asm statements themselves, but after that things worked out fine.

I wouldn't really use this in a finished product, it is very fragile and just happened to work on the version of GCC I was using. The right solution would be for GCC to add an attribute to mark asm statements as potentially throwing.

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

#136
post #126
post #102

Earlier quoted context omitted.

Which part is wild? "Magic" memory addresses are a fairly normal way to communicate with hardware; nowadays there are more layers to how you set up mappings in the MMU etc., but in the old days it was normal for everything to just have a fixed address (e.g. I remember back on the Apple ][ the screen's framebuffer was in a particular memory range, or rather two - to avoid tearing you'd draw on one and then flip which…

The part that surprised me is that this would be the way things worked on a modern C++ compiler without any special flags. The article is about C++, and using "magic" memory addresses doesn't seem at all what I'd expect to be the default way to handle division by zero. From the numerous responses here, it's clear that people interpret my question as about how the hardware itself works, which isn't at all what I was a…

> The article is about C++, and using "magic" memory addresses doesn't seem at all what I'd expect to be the default way to handle division by zero.

They're not saying this is, like, a portable standard way to handle division by zero in C++. You're right that it would be undefined behaviour under the standard (but a C++ compiler for real-mode x86 would be expected to support it, at least implicitly; obviously this specific case is not a particularly useful, but C++ is used in embedded settings and setting a custom interrupt handler is something its users want and expect).

A decent, well-behaved language would do some kind of structured error handling on divide by zero, like throwing an exception. IMO that includes any C++ compiler worth bothering with (though again the standard makes it undefined behaviour so it's possible that some compilers don't). But, the way the runtime of such a decent C++ compiler would actually implement that would be by setting up an interrupt handler for the divide by zero interrupt (that would contain code to construct the exception etc.), and by performing this write to address 0 you're overwriting (the pointer to) that interrupt handler. So, this line of code would cause your program to behave (almost certainly) badly on the next division by zero, even if you were using a well-behaved C++ compiler that normally handled division by zero gracefully.

(OTOH with a maliciously pedantic C++ compiler that division by zero would already be undefined behaviour, so in practice, since most C++ compilers tend to be maliciously pedantic, you might be no worse off than you were before that line).

The original post you replied to was just talking about the somewhat interesting details of what would actually happen because of the quirks of what these addresses are used for on that hardware (e.g. the fact that address 0 is supposed to contain a pointer to the handler, so by setting it to 0 you cause the CPU to start executing the interrupt handler table as code, is kind of interesting - not as a point about C++, but as a point about funny emergent behaviour of hardware), not about what this is specified as doing or the normal way of doing things in C++. I don't know why you were downvoted.

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

#137
post #126
post #102

Earlier quoted context omitted.

Which part is wild? "Magic" memory addresses are a fairly normal way to communicate with hardware; nowadays there are more layers to how you set up mappings in the MMU etc., but in the old days it was normal for everything to just have a fixed address (e.g. I remember back on the Apple ][ the screen's framebuffer was in a particular memory range, or rather two - to avoid tearing you'd draw on one and then flip which…

The part that surprised me is that this would be the way things worked on a modern C++ compiler without any special flags. The article is about C++, and using "magic" memory addresses doesn't seem at all what I'd expect to be the default way to handle division by zero. From the numerous responses here, it's clear that people interpret my question as about how the hardware itself works, which isn't at all what I was a…

The difference between modern days and days of DOS isn't in C/C++ compiler, it's in virtual memory and address space isolation and privilege isolation. So it's not a job of a C/C++ compiler to enforce protection from writing to "special" addresses, because interrupt table updates (and memory-mapped hardware I/O in general) still must happen somewhere (i.e. in kernel, hypervisor, drivers etc) and that code is still written in C/C++, same as in the DOS era.

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

#138
post #123

Earlier quoted context omitted.

That surgeon has undergone several years training, had to go through an exam to be allowed to practice, and is submited to yearly evaluations if they are still allowed to touch that scapel. I would agree the same for developers, if similar practices would be enforced everywhere instead of having people calling themselves enginners just because they like how the word sounds.

Yet surgeons killed people all of the time and had to be forced to use checklists because it turns out credentialism doesn’t prevent mistakes.

Which is also something that we need to add as well, on top of credentialism.

More quality and process validation, less cowboy programming.

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

#139
post #92

Earlier quoted context omitted.

I see you've been lucky enough to only work on systems where CHAR_BIT=8. It's not an alignment issue because there may not be a distinct pointer representation for the other bits, rather than alignment issues where a pointer representation exists even if you can't use it.

Adding to the above, in the 1980s and 1990s it was not uncommon to interface and write code for chips that were 12 bit and 24 bit based (instrumentation accumulators for example, "cheapest sufficient" chips for particular jobs, etc). Today you can still work with (say) TI DSP chips that spit complex FFT pipelines results once per cycle and have absolutely no 8-bit hardware addressing or masking abilities as they're l…

Out of curiosity, how do you deal with I/O on such systems? ISO C simultaneously requires that blocks of memory roundtrip through binary I/O (so it can’t truncate chars to octets) and that fgetc() and friends return unsigned chars cast to an ints or EOF (so you really want the range of int to include the range of unsigned char, even if you could technically depend on the implementation-defined overflow behaviour). Then again, I guess “you don’t” could be a valid answer on a DSP.

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

#140
post #110
post #84

Earlier quoted context omitted.

You can't assume char is one octet . It is one byte by definition. A byte is CHAR_BIT bits, where CHAR_BIT >= 8. (It's exactly 8 on most implementations; DSPs are the most common exception). short and int are both required to be at least 16 bits wide. It's possible for int to be 1 byte (sizeof (int) == 1), but only if CHAR_BIT >= 16.

A clarification: You can certainly assume that char is 8 bits if you don't mind losing portability to a small minority of systems. If I'm being pedantic, I might add something like #if CHAR_BIT != 8 #error "This code assumes 8-bit char" #endif But realistically, if I'm using headers defined by either POSIX or Windows, that's probably enough of a guarantee. (Though I'd still use CHAR_BIT rather than 8 to refer to the…

posix indeed guarantees CHAR_BIT == 8.
Post reply on HN