What are near, far and huge pointers?
In the old days, according to the Turbo C manual, a near pointer was merely 16 bits when your entire code and data fit in the one segment. A far pointer was composed of a segment as well as an offset but no normalisation was performed. And a huge pointer was automatically normalised. Two far pointers could conceivably point to the same location in memory but be different whereas the normalised huge pointers pointing to the same memory location would always be equal.
What is the difference between far pointers and near pointers?
On a 16-bit x86 segmented memory architecture, four registers are used to refer to the respective segments:
- DS → data segment
- CS → code segment
- SS → stack segment
- ES → extra segment
A logical address on this architecture is written segment:offset
. Now to answer the question:
Near pointers refer (as an offset) to the current segment.
Far pointers use segment info and an offset to point across segments. So, to use them, DS or CS must be changed to the specified value, the memory will be dereferenced and then the original value of DS/CS restored. Note that pointer arithmetic on them doesn't modify the segment portion of the pointer, so overflowing the offset will just wrap it around.
And then there are huge pointers, which are normalized to have the highest possible segment for a given address (contrary to far pointers).
On 32-bit and 64-bit architectures, memory models are using segments differently, or not at all.
Near and Far pointers
The near and far keywords have their origin in the segmented memory model that Intel had before. The near pointers could only access a block of memory originally around 64Kb in size called a segment whereas the far pointers could go outside of that range consisting of a segment and offset in that segment. The near pointers were much faster than far pointers so therefore in some contexts it paid off to use them.
Nowadays with virtual memory near and far pointers have no use.
EDIT:Sorry if I am not using the correct terms, but this is how I remembered it when I was working with it back in the day :-)
what is meant by normalization in huge pointers
In the beginning 8086 was an extension of the 8 bit processor 8085. The 8085 could only address 65536 bytes with its 16 bit address bus. When Intel developed the 8086 they wanted the software to be as compatible as possible to the old 8 bit processors, so they introduced the concept of segmented memory addressing. This allowed to run 8 bit software to live in the bigger address range without noticing. The 8086 had a 20 bit address bus and could thus handle up to 1 MB of memory (2^20). Unfortunatly it could not address this memory directly, it had to use the segment registers to do that. The real address was calculated by adding the 16 bit segment value shifted by 4 to the 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 will have noticed, this operation is not bijective, meaning you can generate the real address with other combinations of segment and offset.
0x 1264 0x 1111
+0x 5378 +0x 68A8
--------- --------- etc.
=0x 179B8 =0x 179B8
There are in fact 4096 different combinations possible, because of the 3 overlapping nibbles (3*4 = 12
bits, 2^12 = 4096
) .
The normalized combination is the only one in 4096 possible values that will have the 3 high nibbles of the offset to zero. In our example it will be:
0x 179B
+0x 0008
---------
=0x 179B8
The difference between a far
and a huge
pointer is not in the normalisation, you can have non normalised huge
pointer, it's absolutly allowed. The difference is in the code generated when performing pointer arithmetic. With far pointers when incrementing or adding values to the pointer there will be no overflow handling and you will be only able to handle 64K of memory.
char far *p = (char far *)0x1000FFFF;
p++;
printf("p=%p\n");
will print 1000:0000
For huge pointers the compiler will generate the code necessary to handle the carry over.
char huge *p = (char huge *)0x1000FFFF;
p++;
printf("p=%p\n");
will print 2000:0000
This means you have to be careful when using far or huge pointers as the cost of the arithmetic with them is different.
One should also not forget that most 16 bit compilers had libraries that didn't handle these cases correctly giving sometimes buggy software.
Microsofts real mode compiler didn't handle huge pointers on all its string functions. Borland was even worse as even the mem functions (memcpy
, memset
, etc.) didn't handle offset overflows. That was the reason why it was a good idea to use normalised pointers with these library functions, the likelyhood of offset overflows was lower with them.
Use of type modifiers(near,far,huge) with normal variables
Everything that is placed on the stack can't be far modified.
You can place "non-far variables" in the stack, but not "far variables".
You can place "non-far pointers to non-far data" in the stack, but not "far pointers to non-far data**".
You can place "non-far pointers to far data" in the stack, but not "far pointers to far data".
Try this:
far int var = 0; /* OK */
far int far* far_var_ptr = &var; /* OK: far pointer to far data*/
int far* var_ptr = &var; /* OK: non-far pointer to far data*/ */
int main()
{
int far* var_ptr2 = &var; /* OK: Non-far ptr to far data */
far int far* far_var_ptr2 = &var; /* Fail: far ptr to far data */
far int var2 = 0; /* Fail: far data */
}
The key is that you can't define far data on the stack. Variables on the stack are:
Placed within a defined range of memory
Its exact location depends on the call stack before: it can't be known at compile time
This is not a far data:
int far* var;
It is an non-modified pointer to far data. The pointer itself is just a number not far modified that points to data in a far segment (platform specific).
This is far data:
far int* var;
And this too:
far int far* var;
The storage (far, near, huge) modifier of a variable (or function) is placed before the variable type.
Related Topics
Difference Between Enum and Define Statements
Variable Number of Parameters in Function in C++
What Is a Buffer Overflow and How to Cause One
Taking Input of a String Word by Word
C++ Ifstream Failbit and Badbit
Difference Between Managed C++ and C++
Calling Functions in a Dll from C++
What Happens If 'Throw' Fails to Allocate Memory for Exception Object
What Are Near, Far and Huge Pointers
How to Count Clock Cycles with Rdtsc in Gcc X86
Why Does Boost.Asio Not Support an Event-Based Interface
Invalid Use of Incomplete Type
How to Alter a Float by Its Smallest Increment (Or Close to It)
Difference Between Creating Object with () or Without
Constexpr Const VS Constexpr Variables
Is Std::String Size() a O(1) Operation
Why Do Some Experienced Programmers Write Comparisons with the Value Before the Variable