logoalt Hacker News

jdw64today at 9:43 AM11 repliesview on HN

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


Replies

canucker2016today at 5:40 PM

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...
show 1 reply
jchwtoday at 12:33 PM

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.

show 2 replies
unleadedtoday at 3:24 PM

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".

icelusxltoday at 1:37 PM

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.

bananaflagtoday at 11:43 AM

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).

hnlmorgtoday at 10:32 AM

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.

show 4 replies
kev009today at 10:09 AM

Attention spans were longer.

show 2 replies
rvbatoday at 11:05 AM

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.

markus_zhangtoday at 11:02 AM

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.

show 1 reply
bitwizetoday at 12:03 PM

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.