At the beginning of 8086 there was an extension of the 8-bit processor 8085. The 8085 could only address 65,536 bytes with its 16-bit address bus. When Intel developed the 8086, they wanted the software to be as compatible as possible with older 8-bit processors, so they introduced the concept of segmented memory addressing. This allowed us to run 8-bit software to live in a wider range of addresses without noticing. The 8086 had a 20-bit address bus and could handle up to 1 MB of memory (2 ^ 20). Unfortunately, he could not directly address this memory, for this it was necessary to use segment registers. The real address was calculated by adding the value of the 16-bit segment shifted 4 left, added to the 16-bit offset.
Example: Segment 0x1234 Offset 0x5678 will give the real address 0x 1234 +0x 5678 --------- =0x 179B8
As you noticed, this operation is not bijective, that is, you can generate a real address using other combinations of segment and offset.
0x 1264 0x 1111 +0x 5378 +0x 68A8 --------- --------- etc. =0x 179B8 =0x 179B8
Actually, 4096 different combinations are possible due to 3 overlapping nibbles ( 3*4 = 12 bits, 2^12 = 4096 ). The normalized combination is the only one in 4096 possible values ββthat will have 3 high nibble offsets to zero. In our example, it will be:
0x 179B +0x 0008 --------- =0x 179B8
The difference between the far and huge pointers is not normalized, you can have a huge non-normalized pointer, this is an absolute resolution. The difference lies in the code generated when performing pointer arithmetic. With pointers with a large pointer, adding or adding values ββto the pointer will not handle overflow, and you will be able to process 64 KB of memory.
char far *p = (char far *)0x1000FFFF; p++; printf("p=%p\n");
prints 1000:0000 For huge pointers, the compiler generates the code needed to handle the transfer.
char huge *p = (char huge *)0x1000FFFF; p++; printf("p=%p\n");
will print 2000:0000
This means that you must be careful when using large or large pointers, since the cost of arithmetic with them is different.
Also, one should not forget that most 16-bit compilers had libraries that did not handle these cases correctly, sometimes providing buggy software. The Microsoft real-time compiler did not handle huge pointers to all of its string functions. Borland was even worse because even mem functions ( memcpy , memset , etc.) did not handle offset overflow. That is why it was a good idea to use normalized pointers with these library functions, the probability of offset overflow was lower with them.