6 ms·
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
by sacnoradhq 3y ago
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.
- cjensen 3y agoNot 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.
- sacnoradhq 3y agoFind 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 GNU.
- pxx 3y ago(void *) 0 is a null pointer constant and is not necessarily an all zeros representation. This has been defined virtually forever. https://c-faq.com/null/null2.html https://c-faq.com/null/null2.html https://c-faq.com/null/machexamp.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 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.
- gpderetta 3y ago> sizeof(char) (== 1 almost everywhere) sizeof char is 1 by definition everywhere. /pedantic
- shric 3y ago> sizeof char is 1 by definition everywhere. Parentheses are required around char because it's a type. /pedantic
- cjensen 3y agoThat 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.
- teo_zero 3y agoIt's not that simple! 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.
- shric 3y agoHilariously 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.
- diath 3y agoThat's incorrect, from GCC: error: expected parentheses around type name in sizeof expression.
- dbrower 3y agoIt 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.
- pests 3y agoApparently 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.
- monocasa 3y ago> 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 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.
- gpderetta 3y agomake it a '*(volatile char*)0 = 0' to force the store.
- jcelerier 3y agohttps://gcc.godbolt.org/z/hWEMnjT83 https://gcc.godbolt.org/z/hWEMnjT83
- cjensen 3y agoRegarding 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.
- HybridCurve 3y agoI 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.
- pjmlp 3y agoIn 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.
- saghm 3y agoIf 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?
- wvenable 3y agoBack 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.
- sargstuff 3y agopre-MMU, unless using DEC box.
- inkyoto 3y agoIt is still true even today for microcontrollers – many of them come with a miniscule amount of RAM, no MMU and generally unpredictable memory maps.
- saghm 3y agoSure, 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.
- wvenable 3y agoIn 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.
- layer8 3y agoNot 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.
- kevin_thibedeau 3y agoChar can be the same size as short or int. You can't assume it is one byte.
- _kst_ 3y agoYou 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.
- _kst_ 3y agoA 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 number of bits in a byte.)
- gpderetta 3y agoposix indeed guarantees CHAR_BIT == 8.
- layer8 3y agoYeah, 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.
- jfbastien 3y agoGood thing this was covered in the talk