45

I am very new to C, it's my second high-level programming language after Java. I have gotten most of the basics down, but for whatever reason I am unable to write a single character to screen memory.

This program is compiled using Turbo C for DOS on an Am486-DX4-100 running at 120mhz. The graphics card is a very standard VLB Diamond Multimedia Stealth SE using a Trio32 chip.

For an OS I am running PC-DOS 2000 with an ISO codepage loaded. I am running in standard MDA/CGA/EGA/VGA style 80 column text mode with colour.

Here is the program as I have it written:

#include <stdio.h>

int main(void) {
    unsigned short int *Video = (unsigned short int *)0xB8000;
    *Video = 0x0402;
    getchar();
    return 0;
}

As I stated, I am very new to C, so I apologize if my error seems obvious, I was unable to find a solid source on how to do this that I could understand.

To my knowledge, in real mode on the x86 platform, the screen memory for text mode starts at 0xB8000. Each character is stored in two bytes, one for the character, and one for the background/foreground. The idea is to write the value 0x0402 (which should be a red smiling face) to 0xB8000. This should put it at the top left of the screen.

I have taken into account the possibility that the screen may be scrolling, and thus immediately removing my character upon execution in two ways. To resolve this issue, I have tried:

  • Repeatedly write this value using a loop
  • Write it a bit further down.

I can read and print the value I wrote to memory, so it's obviously still somewhere in memory, but for whatever reason I do not get anything onscreen. I'm obviously doing something wrong, however I do not know what could be the issue. If any other details are needed, please ask. Thank you for any possible help you can give.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Ampera
  • 440
  • 4
  • 13
  • 13
    How does your C implementation map integers to pointers? Do you need to use some kind of `far` pointer keyword? Or does it run in [big/huge unreal mode with 32-bit pointers?](http://wiki.osdev.org/Unreal_Mode). If a C pointer is only 16 bits wide, then it's just an offset within a segment (`ds` by default most of the time), and converting `0xB8000` to a pointer will truncate to 16 bits and give you an offset of `0x8000` relative to `ds`. **TL:DR real mode segmentation doesn't map cleanly to C pointers. Do NOT expect this to be easy**, especially if you don't know both C and x86-16 asm. – Peter Cordes Dec 01 '17 at 07:40
  • Related: [Drawing a character in VGA memory with GNU C inline assembly](https://stackoverflow.com/questions/34748733/drawing-a-character-in-vga-memory-with-gnu-c-inline-assembly/34918617#34918617). I was going to link that answer just for the section about DOS not being the best way to learn asm, but it has the bonus of also showing the asm you need your compiler to generate. – Peter Cordes Dec 01 '17 at 07:44
  • 1
    `0xB8000` is 20 bits wide. (Each hex digit encodes four bits, and there are five of them.) The width of an `unsigned short int` is only 16 bits. The width of a near pointer in most bcc memory models (I think other than huge, but it’s been decades.) is also 16 bits. So, you caused an overflow and wrapped around. I suspect you ended up writing to address `DS:8000` instead. – Davislor Dec 01 '17 at 10:08
  • 9
    Also, is there a prof in India who assigns compilers from the mid-’90s running on a 16-bit OS to beginning CS students, or something? Is there some reason for that? Modern tools like Linux, clang and gcc are completely free. – Davislor Dec 01 '17 at 10:12
  • You might be interested in some of the 3D video code for Turbo C I recently dug up and posted to github: https://github.com/pjc50/ancient-3d-for-turboc/blob/master/glib2.c – pjc50 Dec 01 '17 at 10:16
  • 9
    You are welcome to check out https://retrocomputing.stackexchange.com/ – user11153 Dec 01 '17 at 14:00
  • @pjc50 I'm pretty sure that won't compile on any DOS version of Turbo C, as those by far predate allowing `//` for line-comments in C. [Wikipedia](https://en.wikipedia.org/wiki/Borland_Turbo_C) as being superceded by Turbo C++ in 1990, so C89 (quite possibly with extensions, e.g. for inline assembler) would be the most recent C you could possibly do on Turbo C. [Usage of `//` to indicate comments in C is C99](https://stackoverflow.com/q/2223541/486504). – user Dec 01 '17 at 14:46
  • True, it was the C++ version of Borland's product. – pjc50 Dec 01 '17 at 14:49
  • @Ampera The DOS versions of Turbo C are old enough that they cannot possibly fully support more than C89, and possibly not even that. We are now at [C11](https://en.wikipedia.org/wiki/C11_(C_standard_revision)), which allows such advanced features as for example writing `for(int i=0; i<100; i++){...}` instead of `{int i; for(i=0; i<100; i++){...}}` to get much the same effect. (Caveat: Turbo C may have supported the former syntax as an extension.) – user Dec 01 '17 at 14:49
  • @Davislor: Yes, there are profs somewhere who teach asm using emu8086, and that is the source of 90% of the x86-16 questions on Stack Overflow. It's impossible to hang around in the `[x86]` tag without having to wade through 16-bit crap all the time. I think some people believe that "assembly = 16-bit DOS programming", just because writing in asm by hand was a thing back then, but it isn't now. I'm not sure if people don't even realize that modern CPUs still run machine code, and that it still has the same nearly 1:1 relationship with assembly language, or what. – Peter Cordes Dec 02 '17 at 00:16
  • @PeterCordes I am just a hobbyist who likes to work with older machines. I enjoyed the concept of C, and I like the idea of messing around in real mode. In the future I will be going for things like SDL on C11, but for now I am standing here. – Ampera Dec 02 '17 at 02:00
  • Cool, enjoy your retro computing. If you're into that, then there's some fun stuff to learn. Just keep in mind that hardware and software have changed a lot since then. Knowing your options and still choosing retro 16-bit stuff doesn't bother me. What bugs me is students who don't know any asm and barely know how to program (in C or anything else) being taught on emu8086 / DOS instead of something clean-ish like MIPS (good simulators like SPIM and MARS with toy system calls), or simply x86-64 or 32 Linux. And then there are the people that think want to "write VGA memory" under a modern OS! – Peter Cordes Dec 02 '17 at 02:08
  • Modern compiler-generated code looks very little like typical 8086 code, because [the `loop` instruction is slow and never used](https://stackoverflow.com/questions/35742570/why-is-the-loop-instruction-slow-couldnt-intel-have-implemented-it-efficiently), [writing partial registers (like AH) causes stalls](https://stackoverflow.com/q/41573502/224132), and the whole `int 21h` system call API doesn't exist; you call library functions or Linux system calls. So what people learn about x86 asm is not what they'll be seeing when looking at compiler output. – Peter Cordes Dec 02 '17 at 02:13
  • @PeterCordes Even then, you can totally write asm in `gas`, or inline with `gcc` or `clang`. You can teach the x86_64 abi and write vectorized code by hand, then compare it to the compiler to see how you did. That’s a lot more relevant today than a 16-bit .COM file for MS-DOS. – Davislor Dec 02 '17 at 02:43
  • @Ampera I have fond memories of it. Enjoy your hobby. – Davislor Dec 02 '17 at 02:44
  • 1
    @Davislor: I would absolutely *not* recommend teaching with GNU C inline asm. You have to understand asm, and how compilers "think", to write correct constraints so you don't step on the compiler's toes in the delicate dance required by GNU C inline asm. [You can't even safely use `call` in x86-64 inline asm, because it clobbers the red zone and you can't tell the compiler about it](https://stackoverflow.com/a/34522750/224132). Stand-alone asm that you call from C, absolutely. Write your Factorial or recursive tree-search function asm and call it from C, doing all the printf / scanf in C. – Peter Cordes Dec 02 '17 at 02:50
  • 1
    @Davislor: I don't have the teaching experience to know whether teaching a good / relevant register-args calling convention is better or worse than teaching a simpler stack-args one like i386 SysV. Forcing students to grok the stack has advantages, even if they're learning an obsolete ABI. x86-64 does still need you to grok the stack, but you can write simple functions without touching it. I guess MIPS is like this, and people teach MIPS... I'd definitely agree with teaching beginners x86-64 Linux with NASM or maybe gas. Compiler output uses some 32-bit regs though, so that's "confusing". – Peter Cordes Dec 02 '17 at 02:56
  • 1
    @Davislor: re: beating / helping the compiler for fun and profit: [Why is this C++ code faster than my hand-written assembly for testing the Collatz conjecture?](https://stackoverflow.com/questions/40354978/why-is-this-c-code-faster-than-my-hand-written-assembly-for-testing-the-collat). Definitely a fun time :) And BTW, it's not like compiler output is always the best way to vectorize; unfortunately there are still many missed optimizations. So part of the comparison against the compiler's code should be benchmarking and static analysis by hand or with IACA :P – Peter Cordes Dec 02 '17 at 02:58
  • @PeterCordes All excellent points. My own impression is that there are three things it’s useful to know asm for these days: low-level systems programming, micro-optimization, and understanding how stuff works so it’s not a black box to you any more. You really want to do all three things in a modern toolchain, which might hand beginners some `gcc` boilerplate and say, 'Rewrite this part and benchmark,' but could also use a function call. – Davislor Dec 02 '17 at 03:32
  • 1
    @Davislor: yeah, that's a good summary of the things it's useful to know asm for. Understanding asm and CPU architecture (how caches work, how exactly do atomic operations maintain consistency with MESI, as well as specific details of modern CPUs) is important for performance when you go beyond just using good algorithms. I guess you could understand caching for row-major vs. column-major array indexing and matrix traversal without knowing asm, but it helps. I guess micro-optimization covers a lot of "compiler development" work, improving the optimizer in a compiler. – Peter Cordes Dec 02 '17 at 04:21
  • 2
    @Davislor: Knowing a bit of asm is also really handy to work backward from bug symptoms to what might have caused it in C, so the understanding how stuff works part shouldn't be underestimated. Fun example of this: [unaligned `uint16_t*` can segfault on x86, but x86 can do unaligned loads?](https://stackoverflow.com/questions/47510783/why-does-unaligned-access-to-mmaped-memory-sometimes-segfault-on-amd64) Auto-vectorization breaks this "happens to work" code in an interesting way. – Peter Cordes Dec 02 '17 at 04:24
  • Thanks for all the help. To update, I have gotten a program to fully access text characters. I am going to start learning to use SDL soon, as I do intend to use C for more modern applications. I do have to admit my confusion at segement:offset memory locations, but that's just me being stupid. I only really need to get myself better oriented with the specific way x86 deals with memory. I am a tad more used to big endian 68k, although I have barely any experience there either. – Ampera Dec 02 '17 at 09:00
  • Yeah, even the Intel engineers who designed it have conceded the 8086 memory model was in hindsight a mistake. (Compounded by IBM’s decision to put video memory at 0xA0000 and not, say, have mode switch return a pointer to it.) It had two advantages at the time: in theory, you could put one chunk of code before your data, another after your data, and have two overlapping segments that both contain your code and data. And it made it easier to port Z80 or 8080 code that assumed your computer had a 16-bit memory space, and possibly could bank-switch. – Davislor Dec 02 '17 at 15:06

3 Answers3

56

In real mode to address the first full 1MiB of memory a mechanism called 20-bit segment:offset addressing is used. 0xb8000 is a physical memory address. You need to use something called a far pointer that allows you to address memory with real mode segmentation. The different types of pointers are described in this Stackoverflow Answer

0xb8000 can be represented as a segment of 0xb800 and an offset of 0x0000. The calculation to get physical address is segment*16+offset. 0xb800*16+0x0000=0xb8000. With this in mind you can include dos.h and use the MK_FP C macro to initialize a far pointer to such an address given segment and offset.

From the documentation MK_FP is defined as:

MK_FP() Make a Far Pointer

#include   <dos.h>

void       far *MK_FP(seg,off);
unsigned   seg;                         Segment
unsigned   off;                         Offset

MK_FP() is a macro that makes a far pointer from its component segment 'seg' and offset 'off' parts.

Returns: A far pointer.

Your code could be written like this:

#include <stdio.h>
#include <dos.h>
int main(void) {
    unsigned short int far *Video = (unsigned short int far *)MK_FP(0xB800,0x0000);
    *Video = 0x0402;
    getchar();
    return 0;
}
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • This works and through all these explanations I think I've understood the issue. It's nearly 3 AM where I live, and I will need time to understand this proper. Thanks for the help. – Ampera Dec 01 '17 at 07:48
  • Does casting from a 32-bit integer to a `far *` not work? Like Stacker's answer which uses `char far *Video = (char far *)0xb8000000;` – Peter Cordes Dec 01 '17 at 07:51
  • 1
    @Ampera There is a link to Starman's page in my answer regarding segment:offset addressing in general. You need a good foundation on how the segment:offset addressing works in 16-bit real mode.When you are actually awake I suggest reading over the [Starman link](http://thestarman.pcministry.com/asm/debug/Segments.html) . There is further information in another SO answer about the [types of pointers that 16-bit Turbo-C supports](https://stackoverflow.com/a/19785650/3857942) – Michael Petch Dec 01 '17 at 07:51
  • @PeterCordes Aye, that works too. It's comming back to me as I have some minor experience learning the 68k's assembly language, however that CPU/ISA uses big endianness. – Ampera Dec 01 '17 at 07:54
  • @PeterCordes **Far** pointers are 32-bits in size but the segment is the upper 16-bits and the offset is the lower 16 bits. Stacker's answer does work since the MK_FP effectively creates a pointer that contains 0xb8000000. I was writing up my answer at the same time Stacker did. Only saw a pile of answers after I hit submit. Far pointers are useful if you only need to address a space not exceeding 64kb. Beyond that you need Huge pointers and those come with heftier overhead. – Michael Petch Dec 01 '17 at 07:54
  • @PeterCordes It's done this way so that the `lds` and `les` instructions can be used to load the segment and offset with a single instruction.Far pointers are loaded in this fashion under the hood. – Michael Petch Dec 01 '17 at 07:59
  • @MichaelPetch: Ok, so integer <-> pointer conversions treat the integer side as a linear address? Ah, yes, looks like. I see [the answer you linked earlier](https://stackoverflow.com/questions/3575592/what-are-near-far-and-huge-pointers/19785650#19785650) goes into some of that (but mostly about comparing pointers). This actually came up recently in a question about [what's possible when converting from pointer to `uintptr_t` and back](https://stackoverflow.com/questions/47481834/are-there-any-use-cases-relying-on-integer-representation-of-two-allocations-bei#comment81964190_47481974). – Peter Cordes Dec 01 '17 at 08:02
  • Anyway, yes I expected that the C pointer objects are stored as seg:offset, but I wondered whether the mapping function between `uint32_t` and `char far *` was trivial or whether it linearized / delinearized when casting. (i.e. so `uintptr_t tmp = pointer; memcmp(&tmp, &pointer, 4);` would find them unequal. Surprising to some, but ISO C doesn't guarantee that.) – Peter Cordes Dec 01 '17 at 08:04
  • 1
    @PeterCordes It is a trivial copy that is done (far<=>uint32_t). The uint32_t (and the far pointer) doesn't represent a linear address. It is a 32-bit value where the high 16 bits are the segment and lower 16-bits are the offset. No conversion is done. You can do pointer arithmetic on such a casted pointer as long as you don't carry into the upper 16-bits. – Michael Petch Dec 01 '17 at 08:06
  • Oh, I didn't notice that @stacker's constant wasn't the same as the linear address in the question. Derp. It has extra zeros to put the `0xB8000` in the segment part of `char far *Video`. Seems obvious now that I should have been careful to count the zeros, but for some reason I thought if it was going to work at all, it would work the way I'd suggested on that other question >. – Peter Cordes Dec 01 '17 at 08:11
  • @PeterCordes : Yes exactly. Realized after you may not have counted the zeros properly to begin with (was going to ask you that). So when stored in memory that way LDS and LES can load them directly and set the segment and offset in one go. – Michael Petch Dec 01 '17 at 08:12
  • The design I was imagining would still let you use LDS / LES when loading a `far *`. The linearization would happen *only when casting to/from `uintptr_t`*. Most pointers are stored as pointers, not in integer types (I hope even back then?). As I said, ISO C doesn't guarantee that the binary representation of a pointer matches that of the result of casting it to an integer. But anyway, the actual design for `far *` makes sense because you have to be writing segment-aware code in the first place to use it. I was imagining a pure ISO C implementation with large pointers on segmented x86. – Peter Cordes Dec 01 '17 at 08:17
  • 2
    @PeterCordes : turboCand many early compilers didn't have a `stdint.h`. There was a 3rd party header you could use. That being said the size of a pointer was dependent on memory model (Tiny, Small, Medium were 16-bit pointers). You couldn't just arbitrarily cast a far or huge pointer (which are always 32-bit) to a 16-bit pointer with those models. In compact and large model the default pointer type is `far` (and uintptr_t is size 4). Huge is size 4. You could cast a huge pointer to far but not vice versa unless the far pointer was normalized first. – Michael Petch Dec 01 '17 at 23:05
  • Wow, I would have thought this question was better suited to our RetroComputing sister site :-) – paxdiablo Nov 01 '18 at 01:49
5

The memory segment adress depends on the video mode used:

0xA0000 for EGA/VGA graphics modes (64 KB)
0xB0000 for monochrome text mode (32 KB)
0xB8000 for color text mode and CGA-compatible graphics modes (32 KB)

To directly access vram you need a 32 bit-pointer to hold segement and offset address otherwise you would mess up your heap. This usually leads to undefined behaviour.

char far *Video = (char far *)0xb8000000;

See also: What are near, far and huge pointers?

stacker
  • 68,052
  • 28
  • 140
  • 210
  • This would be a better answer if you explained that `0xb8000000` is the `seg:off` representation of the OP's `0xB8000` linear address. i.e. the upper 16 bits are `0xB8000 >> 4`, and the lower 16 bits are all zero (`0 & 15`). And your first sentence should say "linear address" not "segment address", because those aren't segment-register values, they're what you need to use segmentation to access. (Also, a VGAtext base pointer should maybe be declared as `short` or a struct, not `char`). – Peter Cordes Dec 02 '17 at 03:07
2

As @stacker pointed-out, in the 16-bit environment you need to assign the pointer carefully. AFAIK you need to put FAR keyword (my gosh, what a nostalgia).

Also make sure you don't compile in so-called "Huge" memory model. It's incompatible with far addressing, because every 32-bit pointer is automatically "normalized" to 20 bits. Try selecting "Large" memory model.

valdo
  • 12,632
  • 2
  • 37
  • 67
  • 1
    Wouldn't Huge be exactly what the OP needs to make the code in the question work unmodified? So all pointers can be treated as linear addresses into real-mode physical memory, and have the compiler "work around" segmentation for you by reloading segment registers whenever needed. (oops NVM, that's not how it works. They aren't linear addresses. https://stackoverflow.com/questions/47588486/cannot-write-to-screen-memory-in-c/47588779?noredirect=1#comment82135205_47588779) – Peter Cordes Dec 01 '17 at 08:08
  • @PeterCordes: probably you're right. OTOH since you specify the address directly, and it's already real-mode physical address - why normalize it? – valdo Dec 01 '17 at 08:10
  • Depends what you mean by "physical address". The OP has the linear address of VGA RAM in the question. But you need it in segment:offset format to work as a pointer. (See [discussion on MichealPetch's answer for what I was thinking / talking about](https://stackoverflow.com/questions/47588486/cannot-write-to-screen-memory-in-c/47588780?noredirect=1#comment82135205_47588779): a C implementation which linearized when casting between `uintptr_t` and pointers could have the integer representation of a pointer be the linear address, even if pointers are stored in seg:off format.) – Peter Cordes Dec 01 '17 at 08:22
  • So while that design would have been possible, it isn't the design that was chosen for huge / far pointers in actual compilers. There aren't any compiler options for actual compilers that would make the OP's linear address work, AFAIK. – Peter Cordes Dec 01 '17 at 08:23
  • @PeterCordes I'm not quite sure but I think Watcom/dos4gw did linearize _all_ memory access (including video), although it did actually put the CPU in protected mode which I guess is out of the scope of this question. – jjmontes Dec 01 '17 at 15:57
  • 2
    Yep, I found the reference: "_Under DOS/4GW, the first megabyte of physical memory is mapped as a shared linear address space. This allows your application to access video RAM using a near pointer set to the screen's linear address._". But again, then this is no longer x86 16bit programming. – jjmontes Dec 01 '17 at 16:00
  • @PeterCordes: Although 0x0001:0xFFF0 and 0x1000:0x0000 access the same physical address 0x10000, they are semantically not the same. If the former is loaded into e.g. `ES:BX` and code tries to access `ES:[BX-1]` the resulting access will be to address 0x0FFFF. Attempting the same operation with the latter would yield physical address 0x1FFFF. An implementation could try to make pointers emulate linear behavior, but performance would be dismal. – supercat Dec 01 '17 at 16:02
  • @supercat I strongly suspect that most compilers at the time on the IBM PC and MS-DOS gave you what you asked for, no more and no less. Both because it would be much easier to implement, and because by the time you are manually constructing and managing pointers, you'd probably be expected to know what you wanted and what you're doing anyway. At the time, this kind of knowledge was probably pretty widespread, and certainly not all that difficult to come by; I still have at least one book specifically about Turbo C programming on the IBM PC, and among other things it discusses this very issue! – user Dec 01 '17 at 22:08
  • @PeterCordes : Watcom can generate 16-bit code from its C/C++ compiler and it handles `huge`, `far` and regular `near` pointers in much the same way Turbo-C does. Watcom can also produce code for a flat model (protected mode code like OS2 etc). `huge` pointers don't make much sense here since the video segment doesn't exceed 64kb. `huge` would be overkill since it requires generated code for pointer arithmetic that won't be needed in this case. – Michael Petch Dec 01 '17 at 22:10
  • 2
    @MichaelKjörling: Twentieth-century compiler writers recognized the value in having a family of languages which were tweaked for various platforms, but shared a common base. I think the reason the authors of the C89 Standard only defined two kinds of C implementations (hosted and freestanding) was that not that they thought two was enough, but rather that they didn't think themselves capable of defining everything that would be needed to make implementations suitable for all of the purposes to which C was being put. The 8086 clearly had a natural way of reading a 32-bit value as pointer... – supercat Dec 01 '17 at 22:16
  • ...(i.e. the LDS/LES instructions), and clearly had a relatively clear natural representation for 32-bit integers (at least when an 8087 was attached). Representation-conserving conversions between integers and pointers aren't mandated by the Standard, but would seem the favored approach except on platforms where they would be impractical. – supercat Dec 01 '17 at 22:20
  • @supercat I think you misunderstood what I intended to say. I simply meant that, back in the day, if you asked for a pointer to 0x0001:0xFFF0, you got a pointer to 0x0001:0xFFF0, not 0x1000:0x0000, even if on the particular architecture being targetted, the two ultimately pointed to the same byte of memory. Whichever way one would choose to go, this would likely matter a great deal to any code that works with pointers to memory not allocated through known helper functions (which in C means malloc() and friends), and on personal computers of the time, that was more or less standard practice. – user Dec 01 '17 at 22:47
  • @supercat: I think you're right, any attempt to build a sane pure ISO C implementation with a logically flat pointer space would have much worse performance than what you could get from forcing programmers to write segment-aware code, so there's not much point. Re-normalizing on every pointer arithmetic would be horrible, but the as-if rule would allow pointer increments in loops to just update the offset in many cases. (It would take a smart compiler, and probably lots of extra code-size, to generate versions of blocks for the big and small cases, though. Again, horrible.) – Peter Cordes Dec 01 '17 at 23:34
  • 1
    @PeterCordes: The real value in C's support for weird platforms has never been its ability to make such platforms usable with a wide range of programs, but rather its ability to make such platforms usable with a wide range of *programmers*. If a platform has 12-bit `char`, for example, code written for octet-based storage shouldn't be expected to work with it, but telling a programmer "this platform is pretty normal except that it has 12-bit `char`, 24-bit big-endian `int` and pointers, and 48-bit big-endian `long`" should give that person enough info to write code for it... – supercat Dec 01 '17 at 23:43
  • 1
    ...without having to learn a new instruction set, assembler format, etc. I find the notion the notion that programmers should strive to support every conforming implementation absurd, especially since the authors of the Standard acknowledge that an implementation could be simultaneously conforming but useless. – supercat Dec 01 '17 at 23:45
  • @supercat: I like that idea a lot. Sometimes you can encapsulate something like a rotate using `CHAR_BIT`, but other times it would make the code unreadable, or even unwriteable, to try to be *fully* portable. – Peter Cordes Dec 02 '17 at 00:12
  • @PeterCordes: One major problem with the evolution of C is that it doesn't recognize a distinction between a translator and an execution platform. If one defines a category of "conforming low-level C translator", and requires that a CLLCT must document a full set of platform requirements, then from that it would be practical to define a language in which all behaviors were defined as being--at worst--an unspecified choice from among defined possibilities, except in cases where one of two situations occurs: (1) the CLLCT requests memory for its own exclusive use... – supercat Dec 02 '17 at 17:49
  • ...and doesn't expose it to user code, but something disturbs it; (2) the execution platform behaves in a fashion contrary to its documentation. If either of those conditions occurs, all bets are off, but in all other cases a translator could practically define behavior in terms of either the underlying platform or the laws of mathematics. Guaranteeing that a translator will always behave as a CLLCT even in weird corner cases, would be expensive, but in most cases it should be sufficient to say that someone writing a *quality* CLLCT for a particular purpose should make a bona fide effort... – supercat Dec 02 '17 at 17:54
  • ...to have it behave as one in all cases relevant to that purpose, and programmers should make a bona fide effort to help the compiler recognize cases that matter. Since some compilers aren't used for low-level programming, it's fine that the Standard doesn't mandate everything needed for that purpose. On the other hand, the Standard should recognize that a quality compiler suitable for systems programming on a platform should, whenever remotely practical, be able to do everything a CLLCT for that same platform could do, using code that would work identically on both. – supercat Dec 02 '17 at 18:04
  • @supercat: Interesting points. Related: C has some limitations that asm doesn't. For example, you can't loop over every possible integer value easily in C. An `i <= INT_MAX` or `UINT_MAX` will never become false. But in asm it's easy; just branch on the carry flag. (I guess in C you can use a `do{}while(i++ != 0)` for unsigned, though.) – Peter Cordes Dec 02 '17 at 20:08
  • 1
    @peter - that was annoying enough to me I even asked a [question about it](https://stackoverflow.com/q/40432995/149138). – BeeOnRope Dec 02 '17 at 20:21