That's not just a beautiful high-level description, it's even therapeutic: the next time my HDD is thrashing I'll sit patiently and think that the Page King is too busy ruling his kingdom to grant my humble request right now.
For example, by my forgetting that the "-j" option of "make" had the number of concurrent tasks as an optional parameter, and without that it simply launches every possible compile simultaneously. Paralysis of course: given its uncanny tendency to pick the wrong target I could just about believe that the OOM killer then sniped the King
Before I watch the full talk for the answer (what I've seen so far, he states there's not a right answer)...
I know on some microcontrollers (e.g. Arm) addresses 0x0 and 0x4 are usually used to define the initial stack pointer and entry point and then are never needed again.
Later, you probably want to detect the presence of null pointers by checking if &maybeAStruct is either 0 or valid. If you accidentally test the value at that address, you'll get a non-zero and then run code you didn't intend to run. By defining address 0x0 as value 0, you'll avoid this issue. That has two problems.
Alternatively, you define address 0x0 to erase the initial stack pointer so that a nefarious actor can't somehow memory dump and then find out where the program starts its execution. I don't see the benefit for several reasons.
-----
Scenario 1.
The first problem is that you shouldn't write code poorly. If you meant to check a variable's address and instead check its value, that's on you, your test suite, your compiler, and your debugger (and by extension, your colleagues and educators).
Second, writing to address 0x0 on a Cortex usually involves writing to flash... which is not a temporary/nonpersistent change, more involved than simply writing to an address (you usually need to enable the flash controller and set some peripheral registers), and would usually not be allowed on a production device because that's where the bootloader is or that section of memory has been write protected following good practices.
Scenario 2.
The more I think about this one, the less it makes sense. Anyone with debug port access or a memory dump is already inside the castle, well past any defenses e.g. zeroing the main stack pointer. Plus, the same caveats from Scenario 1 apply: if you COULD write to 0x0, it is almost always a terrible idea.
-----
Grand takeaway? This is probably a talk relevant to PC and not embedded. A Cortex-M would definitely complain about this code. Even on an AVR (atmega) changing 0x0 would be changing R0. I don't even know if that register is directly writeable during execution---probably because it's how you set some bootloader bits---but it's definitely dangerous.
With that in mind, I searched for "virtual address space windows" to figure out what goes in an executable at address 0x0. After reading Microsoft's first two articles, it's still unclear what goes in 0x0. Wikipedia? No answer. Next result? Nothing.
Finally, a page about Windows ME/98/95 states address 0x0 is "available to the process" but is "not writable".
After ten more minutes of fruitless searching, I'm willing to just test what happens, because I expect that's not even writable without side effects during execution.
This compiles and runs but does not display the test string. (If you remove the 0x0 assignment, the string displays; no surprise)
Interestingly, I can't delete test.exe right away. It has stayed open:
ERROR: The process with PID 12776 (child process of PID 19728) could not be terminated.
Not only does PID 19728 not exist, I have to use an administrator console to "taskkill test.exe", so this would probably never belong inside user space code on Windows.
-----
Thus, this short code snippet seems like trouble, and the answer I would hope for as an interviewer is, "yeah, I don't write code like that, sorry."
(Coincidentally, I would say the same in response to the professors giving microcontroller exam questions that try to stump students with multiple casts and dereferences in a single line *a...
Ok, suspicion confirmed after watching: The code snippet itself is basically useless but is a segue into how memory and caches and table walks are arranged as well as "tip of the iceberg" tool and technique recommendations, including cachegrind and gdb and presumably Godbolt, who gets an early mention.
For embedded, this might do something nonawful on a device with memory management e.g. Cortex-A. This exercise is left to the reader.
Moral of the talk? Most of computing is extremely complicated, and you can hand-wave and abstract much of it away unless it's directly in your field. In this case, that would be a compiler engineer or low-level kernel developer.
All C programs do the same thing: look at a character and do nothing with it.
—Peter Weinberger [0]
Humorous 'memory' explaination of various levels of 'memory'[1]
Without reviewing the video, looks like a coding artifact related to segment 64k memory of 8/16/32/64 bit dos/MS windows (vs. arm / sparc with flat address space).
far/near pointer delclaration would clarify things a bit.
This is a c language artifact. Using in C++, one would need to impliment/override the default C++ supplied memory management to avoid memory management gotchas.
The code example was a way to make sure that pointer variable on a non-unified memory architecture was initialized to point within a given memory segment.
aka Reference zero's out the non-unique bits and leaves the "memory reference bits for given memory segment" alone / segmented NULL.
Zeroing out the non-unique memory bits still allows one to make use of the upper address bits to find out where in memory the given
memory segment is (useful for implimentation of setjump(), figuring out machine byte order, 1st time in segment, etc.).
[0] : Expert C Programming, Deep C Secrets by Peter Weinberger
Char pointers have alignment 1 on any conforming implementation and all pointers are aligned at 0. The code will write to "address 0", assuming the compiler hasn't freaked out because (char*)0 is the char null pointer and thus undefined behavior to access. Note the additional caveat that address 0 may not be the first 8 bits of mapped memory (which isn't necessarily addressable).
W.r.t. your Microsoft compiler testcase, your process does receive a Access Violation exception but because there are no registered handlers it is terminated silently. You can check the process exit code for confirmation.
1) evaluation value indefinate of integrand variables: "A" less than/equal to "B" less than/equal to "C" less than/equal to "D". Variable a is start of sequence. Variable D is end of sequence.
2) integrand variables D & A define the continuous range of PC group of bits
3) integrand variables C & B define the cohntinous range of 8 bit char
4) Accessing bits outside the integrand limits implies math equivalent of computer segfault.
In order to not generate a segfault, only positive delta, and epsilon is 0 or 1. aka The "alignment request" implies that A MUST be equal to B; C less than or equal to D.
K&R C short hand for above is anonymous union.[0]
Physics / Mechanical Engineering center of gravity approach rotated 90 & implimented via punch card reader that doesn't skip cards much more interesting physical list processing visual than calculus number line (IMHO). Physical form bit to bulky to carry to/from class room. Fortunately, electrial engineering split the difference via 1/2 way point (45 degree angle). Computer scientests get the autonoma / semantic analysis difference. Software Engineers, by completing the circle with & with out a sigh of "e e e e e e ..."
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).
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 vectors as instructions.
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.
but technically speaking the pointer with a constant zero assigned to it _is_ a null pointer (which can be implemented as whatever bit pattern), independent of the preprocessor macro.
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.
To begin with, sizeof has two syntaxes: the first, which is the one you seem to refer to, is simply
sizeof expression
where expression involves variables and constants, not types. The second is
sizeof (type)
where the parentheses are mandatory.
Then, even in the first syntax, even if sizeof is listed among the operators, even if it doesn't look any different from "pointer operator ", nonetheless it has strange priority rules. For example
sizeof (T) *x
If it was a regular prefix operator obeying priority and right-to-left evaluation, this would mean: dereference x, cast it to T, and return its size. Instead the C standard forces the compiler to interpret it as: take the size of type T and multiply it by x.
Hilariously I've been down voted, even though you absolutely need sizeof (char) because it's a type. Given char x; sizeof x is fine. I know sizeof is an operator. I've been using C for 40 years.
Guess can be taken as a shortened explaination of what a programming language committee is tasked with making happen. Likely why Lisp so successful/useful.
-----
unless initial property is start of dynamic operation, in which case, holding almost anywhere begins at the first operation after the start of the dynamic operation. process / lambda / epsilon calculi is just symbolic math. address 0 static, everything else dynamic.
per math, dimension N is static, to be able to "change things up" in dimension N, need to to be almost everywhere higher than dimension n. Edge cases are weird in any dimension. Guess why logicians just do the equivalent of C's !0
(cast classic logic) A=1
(cast boolean logic) B=0
C statement !(!B == A) hold everywhere and almost everywhere depends on how read C spec to interpret A & B.
It doesn't have to be the NULL macro, which is correctly defined as plain 0.
The literal 0 is treated specially, so this could indeed be one of those 'turns into a weird bit pattern NULL pointers', if such a thing existed in the wild anymore.
But you're correct in that there probably haven't been any since the turn of the century or whenever the last Univac mainframes got turned off.
Apparently according to the c-faqs link elsethread
execl takes a variable-length, null-pointer-terminated list of character pointer arguments, and is correctly called like this:
execl("/bin/sh", "sh", "-c", "date", (char *)0);
Due to ececl being a variadic function it can not take advantage of a prototype to instruct the compiler that one of its arguments needs to be treated as a pointer context.
> 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.
Regarding your PS, I used Borland's Turbo C++ 1.0, and I think you've forgotten that memory models existed. Honestly, that's a good nightmare to forget.
I hated that about DOS, real-mode and BC++. After about 6-8 months of that misery, installing linux and learning to write C code with GCC was the best thing that ever happened to me. I felt like an animal being released from a cage and into the wild.
In those days MS-DOS, Linux was barely usable, when Linux became usable Windows 95 was already around, without those limitations.
My first kernel was 1.0.9 released alongside Slackware 2.0, offering initial support for IDE CD-ROM drives and experimental support for ELF files, by the way.
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 something enabled by default?
Back in the day there were no protections. You could write to any address whether it was used by the CPU for interrupt vectors, part of the OS, hardware addresses, anything.
Sure, but I'm asking if that's something that's enabled by default today or not. I don't see why it's unreasonable for things that were useful "back in the day" to not be available with the default arguments on current versions of compilers but available with certain flags. I'm not sure why my question touched a nerve, because I'm genuinely asking both if I understand correctly and if it's something that needs a flag to enable.
In older CPUs there was no memory management hardware and no virtual memory. You could just read/write in code from any address anywhere and you'd be writing to that actual physical memory in the computer. This wasn't a feature so much as it is a lack of a feature.
Modern CPUs with virtual memory means the question is a lot more complicated. Every process in a modern OS gets it's own address space so you can write to 0 but it could go anywhere (even virtualized to disk) and all the actual hardware is not directly accessible (must go through the OS).
I'm not sure I'd call this ability "useful" except if you're writing an operating system. This is vast simplification but when your computer boots it's effectively in a mode that allows reading/writing to anywhere. The OS kernel has direct access to all the hardware and then it limits access when running user processes.
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.
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).
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.
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 one it was using). And particularly for the CPU, it's hard to see how else it could do customizable interrupt handling - I guess you could have some kind of special API with dedicated CPU instructions or something for "programming" in an interrupt table, but that would be more complex and have no particular benefit. "It reads your table of pointers from this address in memory, in this format" is pretty straightforward and easy to use.
As for why it's address 0, well, it has to go somewhere, every machine has a CPU so everyone needs an interrupt table even if they don't have much memory. And when memory was precious there was no sense wasting even one byte of it; 0 was a real address on your physical memory chip, so why not use it just like any other?
(The fact that it's "address 0" for "division by 0" is just coincidence as far as I can see; division by 0 just happens to be the first kind of possible CPU interrupt. Perhaps it was the most common one?)
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 asking about; I'm aware of how stuff like this works at the assembly level, but my understanding was that in C and C++, trying to write arbitrarily to "special" addresses like that would be considered undefined behavior (often resulting in segfaults). When I read the comment I responded to above, it surprised me, so I wanted to check whether I understood what was said correctly. It's honestly kind of confusing to me that so many people seem very upset by the idea that a stranger on the internet might have a misconception about how hardware abstractions are exposed via compiled code to the point that they feel the need to explain in detail how hardware works but not actually answer the question I asked.
> 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.
> ".. the fact that address 0 is supposed to contain a pointer to the handler .."
What got missed though, is ther has to be an "unused"/"reserve" bit(s) space in order for things to run without requiring additional specific hardware operations.
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.
Not quite, I think. Since this is a char pointer being used, only the first byte of the interrupt address would be zeroed. Since in real mode those are far pointers, the lower byte of the segment would be zeroed. So xx00:xxxx.
But yes, the interrupt table was my first thought when reading the headline.
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 number of bits in a byte.)
Yeah, if you're going to be pedantic, check your facts, see the sibling. Since I'm assuming an 8086 interrupt table, I'm also going to assume 8-bit chars, as that's the x86 addressing model. And dereferencing a null pointer is UB, so you can't count on anything anyway without making further assumptions.
I believe Linux (only in later versions; and a few other OSes) make this page unmappable[1].
That, of course, would be part of the talk: that this is perhaps an OS-specific feature that the original coder was maybe trying to trigger, and that it is still OS-dependent.
By default you can only map addresses over 65536 (root can map anything though). But you can disable the protections with: echo 0 > /proc/sys/vm/mmap_min_addr
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.
I had long wanted to understand the interplay between -funwind-tables, -fasynchronous-unwind-tables and -fexceptions since the last is language specific but the first two are not. GCC docs are not of much help in understanding what exactly is going on in each case so i guess i need to do some research and experimentation.
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 contents. The alternative, getting a segfault because 0 wasn't mapped, is preferable because then you see the problem right away.
Anyhow it's just barely possible that this assignment was a hack to make this sort of code work on a machine where address 0 is (uninitialized) nonzero and can be harmlessly made zero.
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.
Wonderful fun when they shipped a Kerberos library that unconditionally dereferenced a potentially null optional pointer-to-struct, relying on the executable to be set in the “null tolerant” mode!
Many(most?) Cortex-M4 systems boot from the reset vector and stack pointer stored in the first 8B of memory, so it's a valid address on a ton of small systems today.
You can relocate the vector table and to block it off with the MPU if you have one and memory to spare.
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 memory starting at '0' without getting any segfaults.
The PDP-10 did that. There was actually core memory at addresses 0-15 but if you had registers they overlaid that.
Yes, I said if you had registers--if you were cheap you could buy your PDP-10 without registers and all the instructions that referenced registers would use core instead.
Since the registers were in effect just 16 words of fast semiconductor memory overlaying the first 16 words of slow core memory you could do anything in them that you could do in regular memory, including running code out of them.
If you had a small loop that did a lot of iterations you could sometimes get a significant speed boost by copying the loop into the registers and running it there.
You say that as if that's a bad thing – I absolutely need to kept safe from myself, because I need all the help I need to make sure I'm doing things right!
use a malloc that has the extra saftey features, run under an interpreted C, and several other ways to handle it (sandbox program, emulator, etc). Changing the underlying C language should NOT be one of them.
The only way to make C "safe" without modifying the language is to run it in a VM, and even that doesn't fully encompass what people mean by safety.
It's an old language. Some of the fundamental mistakes were just things that were common at the time and only look so bad in hindsight (e.g. strings, locales, nullable pointers, half the k&r standard library). That doesn't mean we can't and shouldn't do better where we can though.
Example, C++/CLI, if certain code patterns are used the code is no longer considered safe from MSIL verifier point of view.
C and C++ targeting WASM, despite all the security message of how great it is, memory locations inside of the same linear memory segment can still be corrupted, thus providing a way to influence the overall execution logic inside of the sandbox.
Oh, so time of 4k memory, have a 4k program and want to strictly reserve/add 3.14159 K to 4k program for automated saftey issues?
At time C was written, swapping in/out of memory measured in literal minutes & when fraction of second of execution time, including swapping in/out of memory, was more than several times the average yearly salary of the day (excluding sneaker net intervention)
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).
I think a more accurate analogy would be some kind of scalpel that would not let the surgeon cut deeper than they needed. So if they needed a 1 cm incision, it would somehow stop the blade form going in 1.1 cm. In that case, some experienced surgeon may say "But I know how to make a 1cm incision!", but I think that the reality is that preventing a mistake from happening is valuable.
You see the same arguments against static analysis, unit tests, strong type systems, etc. The evidence seems to favor systems which prevent errors over artisinal expertise.
I think an even more accurate analogy would be if a surgeon had some kind of scalpel with a 1 cm limit, but if a surgery suddenly changes in the middle, and suddenly requires a deeper incision, the surgeon has to spend a non-zero amount of time re-configuring the scalpel.
This casts flexibility vs safety as a tradeoff, which it is.
Surgeons are constantly killing people[1], and every time, it turns out it was because the surgeon disabled a known, recommended safety rail, and whenever anyone points out that they should stop disabling the safety rails, they insist that they know how to operate without them, it's those other people that don't. Plus it's sooooo inconvenient for an operation[2] to take five more minutes, they're too good to have to deal with that.
The one thing I remember from watching my wife's surgery is that they counted the surgical sponges going in, and counted them coming back out. Because sometimes surgeons would forget and leave a sponge inside and then the person woudl get a really bad infection.
Honestly, checklists and procedure matter more than most people want to admit.
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.
Not at all, that is a guarantee that people actually know how to hold the scapel and can enter the operating room safely.
A brick layer doesn't turn into a Construction Engineer, only because they think they know everything about building houses, and have somehow built their own during long weekends.
Likewise a coder out of a bootcamp, isn't someone versed in what actually entails to be a Software Engineer, regarless how cool the title might sound like.
No, it’s not a guarantee, it’s just a noisy filter with a very high false negative rate and a high enough false positive rate (as visible in malpractice insurance).
yes, compiler will align integers on word boundaries. casting to char, just uses the first 8 bits of integer word. Char was originaly just sematic macro for integer. pointer to char is 8 sequenial bits somewhere in the group of integer spaced bits. if you're lucky, the 8 bits are at the start of the integer group of bits, otherwise, segfault.
Originaly using anonymous unions & placing largest bit count variable as first item in union was the context needed to align the "char" correctly -- vs. casting.
Technically, can point at anything in an unaligned manner, just need to align access to machines address boundary to use a "fetch the value of a complete valid memory alignment address". One can hand roll the appropriate shifts/masking to get at the set of bits loaded (which usually let compiler do). Awk and settable end of line marker good way to safely visualize this.
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 lean mean RISC optimised machines for pure 32 or 64 bit floating point operations.
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.
It depends entirely on the system as a whole, and it might help to start by forgetting about "ISO C" and start thinking about C the triangle.
* C preprocessor - it's a text substition operation that can be non standard
* C "the language" - just the syntax folks, no library functions here.
* C "the standard library" - for many of us the K&R stdlib was just a proof of concept example of how to code a library, feel free to ditch it and write up your own string handling, for example.
Which brings us to, say, a multi channel marine seismic processing system that has an IBM PC type design with a custom motherboard that has six TI DSP boards slotted in and you're writing code for the user interface to a real time signal aquisition and processing system and writing onboard code for the numerical processing on the DSP boards.
Now, each board handles a streamer cable, each cable has a number of microphones, an external boomer in the water is triggered and the reflective soundwaves from the ocean floor, and the soundwaves that penetrate the seafloor and later also partially reflected by density change layers, are all captured by the microphones.
You have keyboard+mouse I/O between the real time window manager user interface, shared memory I/O between the PC memory and memory on the DSP cards, analogue soundwaves going to sample ports on the DSP cards, block memory I/O going from the PC to a SEG-9 tape recorder, prepared lines of memory going to a plotter to build an image ...
There's a lot going on.
But, as far as the code compiled for the DSP boards, that mostly handles I/O by linking reaction code to interrupt triggers - when the sampling hardware interrupts to signal another bit of soundwave from microphone[i] is ready, that's stashed in a FiFo queue to be pushed into the DSP handling pipe and to be saved in a raw sample buffer.
When a raw sample buffer is full an interrupt is triggered to take the entire buffer via DMA transfer to be handled PC side by sending it to the SEG-9 tape "of (raw) record", when a processed sample buffer is full an interrupt is triggered for a different type of DMA transfer that takes processed data to display, printer, and to a different SEG-9 track for processed data.
Not much of this is the standard C library, so you can see why you might write your own low level handling.
Stick to delimited ascii & send over tcp/ip, perhaps use asn/bers specs. -- UTF / json / xml nice unification of everything, as long as don't forget sending UTF flag.
ummm... sounds like a unsigned char same as signed char reference. Lot of fun debugging 32 bit int (big, little and mixed endian) over char absolution position always starts at left and is contiguous.
You're mostly correct but to be pedantic, in C the literal value 0 does not actually mean address 0. The literal value of 0 is an implementation defined value that represents a null pointer, but that value does not have to be address 0, it could be some other address.
This is significant because the following two snippets of code are not required to be equal in C.
> that value does not have to be address 0, it could be some other address.
That's what the spec says, but are there any computers around anymore where nullptr is not zero? The spec should really go with the times IMHO, old hardware can be supported via platform-specific language extensions.
The cleanest way would probably be to get rid of 0 as a null pointer constant, make its use in contexts that expect a pointer illegal and replace it with an explicit keyword. Of course that would break all existing code that uses 0 or (void*)0 instead of NULL, but if you are out to break things anyway you might as well get rid of 40 year old warts.
Well it should be backwards compatible if you just use NULL everywhere where it is appropriate, so you wouldn't even have to sprinkle ifdefs all over the place. Code that uses 0 all over the place would probably have to be updated with the help of compiler errors and a static code analyzer.
> Depending on the ``memory model'' in use, 8086-family processors (PC compatibles) may use 16-bit data pointers and 32-bit function pointers, or vice versa.
And it isn't just zero anyway because there's segmentation in play.
The big place where it happens in modern environments are for pointers to members, where 0 is a bad nullptr value because the member at index 0 is legitimately a thing you want to name. MSVC uses -1 for nullptr in such cases.
somewhat covered in different hacker news post, for C, roughly:
[]
*()++
Technically, can only do address "1" with first bit set as a valid, usable address on hardware allowing addressing smaller than 8 bits (standard hardware epsilon factor).
So really, address one is literally address 0 + epsilon, where epsilon is minimal addressable bit group, typically 8 bit clean without seg fault.
Although, for standard x86, epsilon size would depend on which ring/boot method level & asm addressing method available (8,16,32,64).
It wouldn't work, because in the early days of 4.xbsd, whether page 0 was mapped or not depended on linker options. Originally page 0 was mapped, this led to problems with people being sloppy with null pointers, so the default was changed (so that dereferencing a null pointer, whether reading or writing, would trap). But there was an option o map page 0 to get crappy/broken programs to work.
This was a lot of fun. Reminded me of one of the earliest C programs I wrote when I was learning to code on Win98, before I really knew how computers worked. I was curious: "What does the rest of RAM look like?"
#include <stdio.h>
int main() {
for (int x = 0;;x++) {
putc(*(char*)x);
}
return 0;
}
I was mezmerized by the strings from the uncompressed BIOS ROM being dumped back at me, "American Megatrends".. etc. Eventually this process crashes of course, when it runs past the end of mapped memory locations.
It was great when virtually everything was mapped 1:1 into "real" memory, even video memory. In those days, everything was a memory address, just like "everything is a file".
Then came the realization you could alter any memory location, and further, you could write a tiny TSR to do things...
Not a big fan of the format of the talk. The guy is a good orator, but it takes ages to go to the point. A bit annoying if you're actually interested in the technical part rather than the jokes.
If you use GPT-4 with Browsing turned on and supply it with the title, it will find the page and use the transcript and you can ask it questions. Then you can look at the transcripts to find the spots and verify. Alternatively, if you have an AI-enabled browser you can ask it about the transcript once you open it.
I ask it to quote the section and it helps to find. But if you prefer reading the transcript that’s fine too. It’s not a commandment. It’s my sharing what works for me.
Fair enough. I was not criticising you, sorry if it sounded that way. I am just puzzled by instances where it seems an old fashioned approach (such as control-F in this case) would perform at least as well as fancy (and not always reliable) AI techniques.
The transcript is AI-generated sadly, so it isn't typo-free enough for Cmd-F always. Especially programming content gets messed up. "char" -> "car" and so on and so forth, but GPT-4 will correctly fix all that up.
Your comment misses the point of the talk. It isn't to explain to the viewer what that line of code does, it's to talk about talking about what that line of code does.
The point is the journey and the discussion around what the line of code does, and how that discussion is valuable. The point isn't to tell us what the code itself does.
Ah fuck, I grepped the YouTube transcript for WASM/WebAssembly but I guess youtube's automatic captions aren't very good. I feel silly now for not watching the video before commenting.
Ohly because the VM WebAssembly physical hardware address to running program memory address 0 isn't actually mapped to physical hardware memory address 0.
Note: think there's a way to convert this to some shorter, massively recursive C pointer, to pointer .... to pointer declaration explaination.
Some comments about this being valid on old-school computers, but even in more modern contexts, there are such cases. When programming SPUs on the Cell processor (okay, still a bit old), address 0 was valid memory you could use, in the 256K per-SPU local store.
I've done it, on purpose, to cause a crash, so that it would drop a core file and I could find out exactly how it got to that point in the code. (g++ on an ARM, on an embedded system running embedded Linux, with lots of functions that could be called by more than one thread. "ulimit -c unlimited".)
To me this talk was rather confusing. This statement cannot occur in a well formed standard C or C++ program, and I don’t think that the speaker did a good job conveying this. They also took way too much time to get to the actual point. If the intention was to discuss the particularities of memory mapping on some popular systems, they should have used bitcast from the start.
There are hundreds of instances of (char)0=0; in github, and there are none of the bit_cast variant, so if your goal is to inform people how to read code, starting with the one humans might actually encounter in the wild makes sense.
On Windows, it has a very specific meaning, write to address 0. Which is by default a no-access page, causing an access violation exception, jumping to a user-provided exception handler if present, or 'crashing the program' if not.
198 comments
[ 2.9 ms ] story [ 1639 ms ] threadI know on some microcontrollers (e.g. Arm) addresses 0x0 and 0x4 are usually used to define the initial stack pointer and entry point and then are never needed again.
Later, you probably want to detect the presence of null pointers by checking if &maybeAStruct is either 0 or valid. If you accidentally test the value at that address, you'll get a non-zero and then run code you didn't intend to run. By defining address 0x0 as value 0, you'll avoid this issue. That has two problems.
Alternatively, you define address 0x0 to erase the initial stack pointer so that a nefarious actor can't somehow memory dump and then find out where the program starts its execution. I don't see the benefit for several reasons.
-----
Scenario 1.
The first problem is that you shouldn't write code poorly. If you meant to check a variable's address and instead check its value, that's on you, your test suite, your compiler, and your debugger (and by extension, your colleagues and educators).
Second, writing to address 0x0 on a Cortex usually involves writing to flash... which is not a temporary/nonpersistent change, more involved than simply writing to an address (you usually need to enable the flash controller and set some peripheral registers), and would usually not be allowed on a production device because that's where the bootloader is or that section of memory has been write protected following good practices.
Scenario 2.
The more I think about this one, the less it makes sense. Anyone with debug port access or a memory dump is already inside the castle, well past any defenses e.g. zeroing the main stack pointer. Plus, the same caveats from Scenario 1 apply: if you COULD write to 0x0, it is almost always a terrible idea.
-----
Grand takeaway? This is probably a talk relevant to PC and not embedded. A Cortex-M would definitely complain about this code. Even on an AVR (atmega) changing 0x0 would be changing R0. I don't even know if that register is directly writeable during execution---probably because it's how you set some bootloader bits---but it's definitely dangerous.
With that in mind, I searched for "virtual address space windows" to figure out what goes in an executable at address 0x0. After reading Microsoft's first two articles, it's still unclear what goes in 0x0. Wikipedia? No answer. Next result? Nothing.
Finally, a page about Windows ME/98/95 states address 0x0 is "available to the process" but is "not writable".
After ten more minutes of fruitless searching, I'm willing to just test what happens, because I expect that's not even writable without side effects during execution.
and then, "g++ test.cpp -o test", and ...[1] 869 segmentation fault ./test
Linux? Fail.
-----
"cl test.cpp /link /out:test.exe"...
This compiles and runs but does not display the test string. (If you remove the 0x0 assignment, the string displays; no surprise)
Interestingly, I can't delete test.exe right away. It has stayed open:
ERROR: The process with PID 12776 (child process of PID 19728) could not be terminated.
Not only does PID 19728 not exist, I have to use an administrator console to "taskkill test.exe", so this would probably never belong inside user space code on Windows.
-----
Thus, this short code snippet seems like trouble, and the answer I would hope for as an interviewer is, "yeah, I don't write code like that, sorry."
(Coincidentally, I would say the same in response to the professors giving microcontroller exam questions that try to stump students with multiple casts and dereferences in a single line *a...
For embedded, this might do something nonawful on a device with memory management e.g. Cortex-A. This exercise is left to the reader.
Moral of the talk? Most of computing is extremely complicated, and you can hand-wave and abstract much of it away unless it's directly in your field. In this case, that would be a compiler engineer or low-level kernel developer.
Humorous 'memory' explaination of various levels of 'memory'[1]
Without reviewing the video, looks like a coding artifact related to segment 64k memory of 8/16/32/64 bit dos/MS windows (vs. arm / sparc with flat address space).
far/near pointer delclaration would clarify things a bit.
This is a c language artifact. Using in C++, one would need to impliment/override the default C++ supplied memory management to avoid memory management gotchas.
The code example was a way to make sure that pointer variable on a non-unified memory architecture was initialized to point within a given memory segment.
aka Reference zero's out the non-unique bits and leaves the "memory reference bits for given memory segment" alone / segmented NULL.
Zeroing out the non-unique memory bits still allows one to make use of the upper address bits to find out where in memory the given
memory segment is (useful for implimentation of setjump(), figuring out machine byte order, 1st time in segment, etc.).
[0] : Expert C Programming, Deep C Secrets by Peter Weinberger
[1] : http://www.gnu.org/fun/jokes/paging.game.html
The code can do something shifty at run time, without a power surge, to the address reference before the address is de-referenced
For all the gory details see Unwinding the Stack: Exploring How C++ Exceptions Work on Windows - https://www.youtube.com/watch?v=COEv2kq_Ht8
Until accessed, a memory location & variable location can be expressed as an indefinate integral.
Memory location access request implies definate integral.
1) evaluation value indefinate of integrand variables: "A" less than/equal to "B" less than/equal to "C" less than/equal to "D". Variable a is start of sequence. Variable D is end of sequence.
2) integrand variables D & A define the continuous range of PC group of bits
3) integrand variables C & B define the cohntinous range of 8 bit char
4) Accessing bits outside the integrand limits implies math equivalent of computer segfault.
In order to not generate a segfault, only positive delta, and epsilon is 0 or 1. aka The "alignment request" implies that A MUST be equal to B; C less than or equal to D.
K&R C short hand for above is anonymous union.[0]
Physics / Mechanical Engineering center of gravity approach rotated 90 & implimented via punch card reader that doesn't skip cards much more interesting physical list processing visual than calculus number line (IMHO). Physical form bit to bulky to carry to/from class room. Fortunately, electrial engineering split the difference via 1/2 way point (45 degree angle). Computer scientests get the autonoma / semantic analysis difference. Software Engineers, by completing the circle with & with out a sigh of "e e e e e e ..."
[0] : http://www.catb.org/esr/structure-packing/
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 GNU.
https://c-faq.com/null/null2.html
https://c-faq.com/null/machexamp.html
Actual ways to do what you want to do are described in
https://c-faq.com/null/accessloc0.html
but technically speaking the pointer with a constant zero assigned to it _is_ a null pointer (which can be implemented as whatever bit pattern), independent of the preprocessor macro.
sizeof char is 1 by definition everywhere.
/pedantic
Parentheses are required around char because it's a type.
/pedantic
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.
To begin with, sizeof has two syntaxes: the first, which is the one you seem to refer to, is simply
where expression involves variables and constants, not types. The second is where the parentheses are mandatory.Then, even in the first syntax, even if sizeof is listed among the operators, even if it doesn't look any different from "pointer operator ", nonetheless it has strange priority rules. For example
If it was a regular prefix operator obeying priority and right-to-left evaluation, this would mean: dereference x, cast it to T, and return its size. Instead the C standard forces the compiler to interpret it as: take the size of type T and multiply it by x.-----
unless initial property is start of dynamic operation, in which case, holding almost anywhere begins at the first operation after the start of the dynamic operation. process / lambda / epsilon calculi is just symbolic math. address 0 static, everything else dynamic.
per math, dimension N is static, to be able to "change things up" in dimension N, need to to be almost everywhere higher than dimension n. Edge cases are weird in any dimension. Guess why logicians just do the equivalent of C's !0
(cast classic logic) A=1 (cast boolean logic) B=0
C statement !(!B == A) hold everywhere and almost everywhere depends on how read C spec to interpret A & B.
The literal 0 is treated specially, so this could indeed be one of those 'turns into a weird bit pattern NULL pointers', if such a thing existed in the wild anymore.
But you're correct in that there probably haven't been any since the turn of the century or whenever the last Univac mainframes got turned off.
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.
My first kernel was 1.0.9 released alongside Slackware 2.0, offering initial support for IDE CD-ROM drives and experimental support for ELF files, by the way.
Modern CPUs with virtual memory means the question is a lot more complicated. Every process in a modern OS gets it's own address space so you can write to 0 but it could go anywhere (even virtualized to disk) and all the actual hardware is not directly accessible (must go through the OS).
I'm not sure I'd call this ability "useful" except if you're writing an operating system. This is vast simplification but when your computer boots it's effectively in a mode that allows reading/writing to anywhere. The OS kernel has direct access to all the hardware and then it limits access when running user processes.
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).
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.
As for why it's address 0, well, it has to go somewhere, every machine has a CPU so everyone needs an interrupt table even if they don't have much memory. And when memory was precious there was no sense wasting even one byte of it; 0 was a real address on your physical memory chip, so why not use it just like any other?
(The fact that it's "address 0" for "division by 0" is just coincidence as far as I can see; division by 0 just happens to be the first kind of possible CPU interrupt. Perhaps it was the most common one?)
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 asking about; I'm aware of how stuff like this works at the assembly level, but my understanding was that in C and C++, trying to write arbitrarily to "special" addresses like that would be considered undefined behavior (often resulting in segfaults). When I read the comment I responded to above, it surprised me, so I wanted to check whether I understood what was said correctly. It's honestly kind of confusing to me that so many people seem very upset by the idea that a stranger on the internet might have a misconception about how hardware abstractions are exposed via compiled code to the point that they feel the need to explain in detail how hardware works but not actually answer the question I asked.
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.
What got missed though, is ther has to be an "unused"/"reserve" bit(s) space in order for things to run without requiring additional specific hardware operations.
DEC provided the necessary hardware MMU to do actual real time multi-processing/multi-user access in feasibile/practical manner.
But yes, the interrupt table was my first thought when reading the headline.
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.
If I'm being pedantic, I might add something like
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 number of bits in a byte.)That, of course, would be part of the talk: that this is perhaps an OS-specific feature that the original coder was maybe trying to trigger, and that it is still OS-dependent.
[1]: https://en.wikipedia.org/wiki/Zero_page states this to be the case, but mmap(2) seems to disagree / suggests 0 is mappable.
My objective was to test the crash reporter tool.
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.
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.
I had long wanted to understand the interplay between -funwind-tables, -fasynchronous-unwind-tables and -fexceptions since the last is language specific but the first two are not. GCC docs are not of much help in understanding what exactly is going on in each case so i guess i need to do some research and experimentation.
Anyhow it's just barely possible that this assignment was a hack to make this sort of code work on a machine where address 0 is (uninitialized) nonzero and can be harmlessly made zero.
Certain higher ups determined that behavior was a business requirement when I refused to reimplement it on a new platform.
https://docstore.mik.ua/manuals/hp-ux/en/B2355-60130/chatr_p...
Wonderful fun when they shipped a Kerberos library that unconditionally dereferenced a potentially null optional pointer-to-struct, relying on the executable to be set in the “null tolerant” mode!
You can relocate the vector table and to block it off with the MPU if you have one and memory to spare.
Reference if you don’t get the joke: https://www.reddit.com/r/shittyprogramming/comments/3bmszo/t...
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 memory starting at '0' without getting any segfaults.
Yes, I said if you had registers--if you were cheap you could buy your PDP-10 without registers and all the instructions that referenced registers would use core instead.
Since the registers were in effect just 16 words of fast semiconductor memory overlaying the first 16 words of slow core memory you could do anything in them that you could do in regular memory, including running code out of them.
If you had a small loop that did a lot of iterations you could sometimes get a significant speed boost by copying the loop into the registers and running it there.
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,
It's an old language. Some of the fundamental mistakes were just things that were common at the time and only look so bad in hindsight (e.g. strings, locales, nullable pointers, half the k&r standard library). That doesn't mean we can't and shouldn't do better where we can though.
C and C++ targeting WASM, despite all the security message of how great it is, memory locations inside of the same linear memory segment can still be corrupted, thus providing a way to influence the overall execution logic inside of the sandbox.
At time C was written, swapping in/out of memory measured in literal minutes & when fraction of second of execution time, including swapping in/out of memory, was more than several times the average yearly salary of the day (excluding sneaker net intervention)
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).
You see the same arguments against static analysis, unit tests, strong type systems, etc. The evidence seems to favor systems which prevent errors over artisinal expertise.
This casts flexibility vs safety as a tradeoff, which it is.
Surgeons are constantly killing people[1], and every time, it turns out it was because the surgeon disabled a known, recommended safety rail, and whenever anyone points out that they should stop disabling the safety rails, they insist that they know how to operate without them, it's those other people that don't. Plus it's sooooo inconvenient for an operation[2] to take five more minutes, they're too good to have to deal with that.
[1] introducing security vulnerabilities
[2] code changeset submission
Honestly, checklists and procedure matter more than most people want to admit.
1] introducing security vulnerabilities
[2] code changeset submission
This is my favorite one
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.
More quality and process validation, less cowboy programming.
A brick layer doesn't turn into a Construction Engineer, only because they think they know everything about building houses, and have somehow built their own during long weekends.
Likewise a coder out of a bootcamp, isn't someone versed in what actually entails to be a Software Engineer, regarless how cool the title might sound like.
Originaly using anonymous unions & placing largest bit count variable as first item in union was the context needed to align the "char" correctly -- vs. casting.
Technically, can point at anything in an unaligned manner, just need to align access to machines address boundary to use a "fetch the value of a complete valid memory alignment address". One can hand roll the appropriate shifts/masking to get at the set of bits loaded (which usually let compiler do). Awk and settable end of line marker good way to safely visualize this.
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 lean mean RISC optimised machines for pure 32 or 64 bit floating point operations.
They have C compilers and CHAR_BIT==32 (or 64).
* C preprocessor - it's a text substition operation that can be non standard
* C "the language" - just the syntax folks, no library functions here.
* C "the standard library" - for many of us the K&R stdlib was just a proof of concept example of how to code a library, feel free to ditch it and write up your own string handling, for example.
Which brings us to, say, a multi channel marine seismic processing system that has an IBM PC type design with a custom motherboard that has six TI DSP boards slotted in and you're writing code for the user interface to a real time signal aquisition and processing system and writing onboard code for the numerical processing on the DSP boards.
Now, each board handles a streamer cable, each cable has a number of microphones, an external boomer in the water is triggered and the reflective soundwaves from the ocean floor, and the soundwaves that penetrate the seafloor and later also partially reflected by density change layers, are all captured by the microphones.
You have keyboard+mouse I/O between the real time window manager user interface, shared memory I/O between the PC memory and memory on the DSP cards, analogue soundwaves going to sample ports on the DSP cards, block memory I/O going from the PC to a SEG-9 tape recorder, prepared lines of memory going to a plotter to build an image ...
There's a lot going on.
But, as far as the code compiled for the DSP boards, that mostly handles I/O by linking reaction code to interrupt triggers - when the sampling hardware interrupts to signal another bit of soundwave from microphone[i] is ready, that's stashed in a FiFo queue to be pushed into the DSP handling pipe and to be saved in a raw sample buffer.
When a raw sample buffer is full an interrupt is triggered to take the entire buffer via DMA transfer to be handled PC side by sending it to the SEG-9 tape "of (raw) record", when a processed sample buffer is full an interrupt is triggered for a different type of DMA transfer that takes processed data to display, printer, and to a different SEG-9 track for processed data.
Not much of this is the standard C library, so you can see why you might write your own low level handling.
This is significant because the following two snippets of code are not required to be equal in C.
char* c = (char*)(0);
char* d;
memset(&d, 0, sizeof(d));
c == d; // this does not have to be true
That's what the spec says, but are there any computers around anymore where nullptr is not zero? The spec should really go with the times IMHO, old hardware can be supported via platform-specific language extensions.
Yeah that's absolutely fair. I actually can't think of a single platform, embedded or otherwise, old or new, where the null pointer is not address 0.
> Depending on the ``memory model'' in use, 8086-family processors (PC compatibles) may use 16-bit data pointers and 32-bit function pointers, or vice versa.
And it isn't just zero anyway because there's segmentation in play.
So really, address one is literally address 0 + epsilon, where epsilon is minimal addressable bit group, typically 8 bit clean without seg fault.
Although, for standard x86, epsilon size would depend on which ring/boot method level & asm addressing method available (8,16,32,64).
That's also totally valid on WebAssembly btw, address 0 is a regular read/write location.
Then came the realization you could alter any memory location, and further, you could write a tiny TSR to do things...
Where along the determinate in the matrix?
do you mean the browsing plugin, or using base GPT-4?
The point is the journey and the discussion around what the line of code does, and how that discussion is valuable. The point isn't to tell us what the code itself does.
Note: think there's a way to convert this to some shorter, massively recursive C pointer, to pointer .... to pointer declaration explaination.
(obligatory in C and OS theory, other languages might differ)
There are hundreds of instances of (char)0=0; in github, and there are none of the bit_cast variant, so if your goal is to inform people how to read code, starting with the one humans might actually encounter in the wild makes sense.
Not true. It's illegal to execute that statement because of undefined behavior. But it's legal to have: if (false) { *(char*)0 = 0; }
(And of course it's legal to #ifdef it out or comment it out, but that's too much cheating.)
What's passed in there? Some googling let me know it's used on macOS, but every result was too generic to be helpful.
https://stackoverflow.com/questions/29909115/are-there-any-o...