← Back to context

Comment by jdw64

9 hours ago

Sometimes I think that if it were the old days, I probably wouldn't have been able to program. I remember that these days we program on top of 64bit virtual addresses, but how did developers do it back then

Some of this was automatically handled by the compiler and wouldn't have been an issue. Current x86-64 ABIs, for instance, require function entry to use specific forms annotated by metadata to support stack walking to support exception handling. Like the far entry here, this is invisible to most programmers -- the compiler does it for you.

Similarly, while locking and unlocking memory blocks is no longer generally a concern, most programs still deal with files, and graphics programs still have to call map/unmap functions to access graphics data. All the same tools apply -- helper functions/libraries, RAII, and leak/sanitizer tools to dynamically detect usage errors.

As someone who grew up coding after it was mostly 32-bit, I can't say this with certainty, but my gut feeling is that paradoxically you would have and it would've made you stronger.

  • I think it'd be mixed.

    I think the knowledge of underlying hardware is useful and good to know.

    But also that sort of knowledge got dated pretty quickly in the early computer era. Further, the capabilities of things like optimizing compilers quickly got to a point where they'd outpace most hand written assembly. Today, it's basically just floating point operations where you can still do better than a compiler.

    In the early days, you'd have the correct impression that the C compilers spat out utter garbage which was a lot slower than what you could hand craft. As optimization techniques got better and better, the work you did because the compiler was dumb ultimately would have gotten in the way.

  • Exactly. I'd argue that all those programming Gods and Gods because they went through that period. Whatever didn't kill them made them stronger. We should replicate that experience by deliberately writing in low level C and assembly for a few years.

It's easy when it's the only way to get things done. Think about how nobody who was learning programming before 2023 was seriously thinking "This would be so much easier if the computer wrote it all for me".

You just had to live with the constraints.

It biased your selection of data structures and algorithms.

Max 64KB array size meant pointers to allocated structs and linked lists were much more popular back then versus 1 large array of structs.

The Win16 HANDLE memory allocation also meant you had to worry about how you handle structs which had pointers to others structs (a FAR ptr may not be a stable value, unless you locked the HANDLE for the duration of the allocation)

Then you had to worry about stuff that no college programming book talked about (ignore the lack of error checking):

  char FAR *p;
  char FAR *mem = farmalloc(65536);

  for (p = &mem[65535]; p >= &mem[0]; p--) {
    dostuff(p);
  }

Welcome to an infinite loop...

  •   char FAR *p;
      char FAR *mem = farmalloc(65536);
    
      for (p = &mem[65535]; p >= &mem[0]; p--) {
        dostuff(p);
      }
    

    Nice one.

    To be fair to Windows, good C courses should still teach this, but I'm not sure if they do :-)

    It's UB to set a pointer to before the first element of an array, or after the last element plus one. So, if it knows the call to farmalloc/malloc returns the start of an object, a modern C compiler on a modern architecture may, in principle, optimise the above to an infinite loop.

    I've seen something similar on architectures (long ago) where a zero-bit-pattern pointer was a valid memory address you might actually access. Of course p-1 is not less than p when p is zero.

Memory mapping/bank switching was fairly common on 8-bit and 16-bit systems, where a small memory window was used to select different memory banks, allowing a program to access more memory in chunks.

Game consoles like NES, SNES and Game Boy had additional hardware built in the cartridge to support memory mapping/bank switching.

For PCs, EMS (memory) provided a similar concept. It reserved a 64 kB window divided in 16 kB pages in the first 1 MB and allowed to map up to 32 MB.

I first found out about segmenting in 16 bit systems in 2016 by reading a lively explanation from an older edition of Duntemann's Assembly Language Step by Step (the newer editions focus largely on Linux and 32/64-bit systems).

16 bit programs used 16 bit addresses, generally speaking.

Even with 32bit systems where you’d want more than 4GB RAM, application software still had 32 bit addresses (and thus 4GB memory limit).

I think it was a lot more common for 8bit systems to allow for 16 bit addressing though.

It’s been a while though. So hopefully I’m not misremembering things.

  • > I think it was a lot more common for 8bit systems to allow for 16 bit addressing though.

    The 6502 and Z80 could use 16 bit addressing to access up to 64kb of memory. The 6502 had various other addressing systems, including iirc 8 bits, but none of them were wider tha 16 bits.

    • Oh yeah. I had loads of 6502 and Z80 systems (still do in fact). Can’t believe I forgot about that!

      Though in fairness, I do mostly now just use those systems to teach my kids BASIC

      1 reply →

    • Well, most of the addressing modes of the Z80 used a 16-bit register pair (i.e. 0 to 64K-1 bytes) to address, the 6502 used a somewhat stranger set of addressing modes, but once again you could address 0 to 64K-1 bytes.

  • You had to deal with two flavors of pointer, near and far. Far pointers came with segment selector, for accessing more than 64k. Your choice of memory model influenced the defaults. You might use near pointers for internal references in a module, and far pointers for external references.

    • I guess it was awkward to use languages that had higher level than assembly in order to write 16-bit programs that required more than 64KiB of memory. And also not quite portable, since they were all tied to x86 CPU. Those were messy times I guess. A somewhat similar story was 32-bit PAE, where the the CPU could address more than 4GiB physical memory, but software was still 32-bit and virtual addresses were capped at 4GiB. Linus was right that you must have more virtual memory (preferably 10+ times more) than physical, otherwise you have to jump through hoops. https://cl4ssic4l.wordpress.com/2011/05/24/linus-torvalds-ab...

      1 reply →

  • And the 32-bit 4GB limit was often really "just a bit under 2GB" depending on the hardware, OS, etc

  • Not really. 16-bit programs on x86 used 32-bit pointers (effectively 20-bit due to the segment mechanism).

    8-bit microprocessors used 16-bit addresses.

Attention spans were longer.

  • I've been wondering about this lately. As a kid, I spent hour upon hour learning about computing: typing in Basic code from a magazine into a Commodore 64, playing with music on an Atari STe, learning my way around a DOS command line, dabbling with 3D modelling... just so much stuff that my own kids would never have the patience for.

    I wonder if it's just that kids today (gods that makes me sound old!) are constantly surrounded by entertaining things to do - gaming, TV/films, music, social media.

    • I have been shielding my 6 years old son from electronics, except 40 minutes of TV twice a week. I have no idea how to grow his patience and perseverance, though. He is like me, who doesn't have a lot of patience to begin with, so I can't really guide him through some of the situations. We have been taking him to some activities as well as reading to him but nothing really sticks.

      I just hope eventually he loves reading and learns in a more traditional way instead of from laptops and pads.

      1 reply →

    • I think that's actually a pretty accurate observation. I'm not a cognitive science expert, so I don't know the details, but there have been articles about 'popcorn brain' due to sustained attention issues, right? Personally, I use LLMs for coding quite often (in my environment, I'm often forced to use them). Compared to the past, when I use an LLM, the answers come immediately, so it seems harder to focus deeply than before. The generation younger than me, which is more focused on Shorts, probably has it even worse

    • I think it's an adaptation. Instead of living in a world with limited valuable information we're now living at the end of a firehose of never-ending near-useless information which has to be filtered at high speed.

    • Thats correct - and I notice that on myself. There are just much more things reachable at any point in time compared to our youth it takes real effort to focus.

  • I have been wondering how to train my 6-year old son and myself to increase my attention span.

    Some rules are obvious -- cutoff mobiles and pads completely (he doesn't have access to them so it's for me), sit in the library and study from books (I believe this is even possible for programming topics as I can write on paper). Basically, cutting off everything electronics definitely helps -- even putting my phone in the bag improves productivity significantly.

    But the problem is, my son is unruly. If I put him in the library, most likely he runs around and messes things up, which ends up we leave early without doing anything.

    • > But the problem is, my son is unruly. If I put him in the library, most likely he runs around and messes things up, which ends up we leave early without doing anything.

      Some potential ideas to explore. Take what you want, leave what you don't.

      a) if you're training for attention span, make sure the target is appropriate and also within reach of your child.

      b) have a plan for the visit: when I helped at a school library, classes for kids in your kid's age group would come in, the librarian would read them a story, then the kids would look for a book, check out at the desk and read (or look at the book anyway) quietly until the end of the visit. I think we'd get about 40 minutes for a visit. Most days, at least some of the kids would be getting ansy before it was time to go.

      c) Plan around your kid's activity needs. Some kids will do long still antention tasks better after doing some amount of physical activity. Some kids will do these kinds of things better after a meal. Some will do it better in the morning or the afternoon. Many kids will have a harder time if the library visit was a surprise. You know your kid, try to have your library visits when they're likely to work well. If he likes story time, try to visit when there's a story time available.

      d) don't expect that you can both go to the library and work independently. You're going to the library with him, and he's going to need you to help him out for much of the time. But you might be able to find him a book together, then find you a book together, then sit down and read for a bit together.

      e) if all you can get done is finding a book, no big deal. You can read at home too.

      If a lion can figure out how to behave in the library, so can your kid ;) https://www.michelleknudsen.com/library_lion_77788.htm

You had to figure out so much on your own back then - and reinvent the wheel.

For me it is fascinating how today I can learn a foreign language, or how to code by interacting with the LLM.

I think they learned by reading books such as Undocumented Windows or Windows Internals (not to be confused with Windows NT internals), and Microsoft documents.

In fact, I’d argue it was more fun than programming Javascript these days.

  • It wasn't really the 'Undocumented' and 'Internals' books. Pretty much everything in the headlined article was to be found in the SDK, Microsoft Press publications, and in many third party books about DOS+Windows programming.

    Petzold's Programming Windows book, for example, devoted an entire chapter (chapter 7) to memory management, with diagrams and examples. In the 2nd edition (which I just pulled off the shelf to check) that chapter runs to some 40 pages.

16-bit x86 processors took 20-bit pointers, expressed as a 16-bit segment and a 16-bit offset. The segment was shifted four bits left and then the offset added. Which means there are lots of different segment:offset pointers that point to the same address. Segments are loaded into a segment register (one of CS, DS, ES, or SS) and then combined with an offset pointer in another register to create a pointer in this way. For example, 1e37:0008 would become 1e378.

It's complicated and janky as all get-out, but it made more sense if you were coming from 8080/Z80 development, as this was a scheme to ensure some degree of compatibility with 16-bit 8080 addressing while providing access to much more memory. 8086 was not binary compatible with 8080, but was designed so that 8080 programs could be machine converted to 8086 ones.

In languages like C, this took the form of three different types of pointers: NEAR, FAR, and HUGE. NEAR pointers were 16-bit offsets only, and dereferenced with respect to the current segment (usually in DS). FAR pointers were full segment:offset pairs but pointer arithmetic was only done on the offset which meant objects could be 64K max. HUGE pointers allowed for objects larger than 64k but at a significant performance cost.