What is guaranteed about the size of a function pointer? - c

In C, I need to know the size of a struct, which has function pointers in it. Can I be guaranteed that on all platforms and architectures:
the size of a void* is the same size as a function pointer?
the size of the function pointer does not differ due to its return type?
the size of the function pointer does not differ due to its parameter types?
I assume the answer is yes to all of these, but I want to be sure. For context, I'm calling sizeof(struct mystruct) and nothing more.

From C99 spec, section 6.2.5, paragraph 27:
A pointer to void shall have the same
representation and alignment
requirements as a pointer to a
character type. Similarly, pointers
to qualified or unqualified versions of
compatible types shall have the same
representation and alignment
requirements. All pointers to
structure types shall have the same
representation and alignment
requirements as each other. All
pointers to union types shall have the
same representation and alignment
requirements as each other. Pointers
to other types need not have the same
representation or alignment
requirements.
So no; no guarantee that a void * can hold a function pointer.
And section 6.3.2.3, paragraph 8:
A pointer to a function of one type
may be converted to a pointer to a
function of another type and back
again; the result shall compare equal
to the original pointer.
implying that one function pointer type can hold any other function pointer value. Technically, that's not the same as guaranteeing that function-pointer types can't vary in size, merely that their values occupy the same range as each other.

No, no, no.
C doesn't favour Harvard architectures with different code and data pointer sizes, because ideally when programming for such an architecture you want to store data in program memory (string literals and the like), and to do that you'd need object pointers into the code space. But it doesn't forbid them, so as far as the standard is concerned function pointers can refer to an address space which has a different size from the data address space.
However, any function pointer can be cast to another function pointer type[*] and back without trashing the value, in the same way that any object pointer can be cast to void* and back. So it would be rather surprising for function pointers to vary in size according to their signature. There's no obvious "use" for the extra space, if you have to be able to somehow store the same value in less space and then retrieve it when cast back.
[*] Thanks, schot.

In addition to the other answers, Wikipedia says this:
http://en.wikipedia.org/wiki/Function_pointer
Although function pointers in C and
C++ can be implemented as simple
addresses, so that typically
sizeof(Fx)==sizeof(void *), member
pointers in C++ are often implemented
as "fat pointers", typically two or
three times the size of a simple
function pointer, in order to deal
with virtual inheritance.
A function pointer is an abstraction. As long as the requirements of the standard are fulfilled, anything is possible. I.e. if you have less than 256 functions in your program, function pointers could be implemented by using a single byte with the value 0 for NULL and the values 1 to 255 as the index into a table with the physical addresses. If you exceed 255 functions, it could be extended to use 2 bytes.

There is a practical example of differing sizes that used to be common. In the MS-DOS & early Windows C programming, in the "medium" memory model you had 16-bit data pointers but 32-bit function pointers, and the "compact" memory model was the other way round.

Related

Why exactly it is invalid to convert a function pointer to a pointer to void, or vice versa? [duplicate]

I have read that converting a function pointer to a data pointer and vice versa works on most platforms but is not guaranteed to work. Why is this the case? Shouldn't both be simply addresses into main memory and therefore be compatible?
An architecture doesn't have to store code and data in the same memory. With a Harvard architecture, code and data are stored in completely different memory. Most architectures are Von Neumann architectures with code and data in the same memory but C doesn't limit itself to only certain types of architectures if at all possible.
Some computers have (had) separate address spaces for code and data. On such hardware it just doesn't work.
The language is designed not only for current desktop applications, but to allow it to be implemented on a large set of hardware.
It seems like the C language committee never intended void* to be a pointer to function, they just wanted a generic pointer to objects.
The C99 Rationale says:
6.3.2.3 Pointers
C has now been implemented on a wide range of architectures. While some of these
architectures feature uniform pointers which are the size of some integer type, maximally
portable code cannot assume any necessary correspondence between different pointer types and the integer types. On some implementations, pointers can even be wider than any integer type.
The use of void* (“pointer to void”) as a generic object pointer type is an invention of the C89 Committee. Adoption of this type was stimulated by the desire to specify function prototype arguments that either quietly convert arbitrary pointers (as in fread) or complain if the argument type does not exactly match (as in strcmp). Nothing is said about pointers to functions, which may be incommensurate with object pointers and/or integers.
Note Nothing is said about pointers to functions in the last paragraph. They might be different from other pointers, and the committee is aware of that.
For those who remember MS-DOS, Windows 3.1 and older the answer is quite easy. All of these used to support several different memory models, with varying combinations of characteristics for code and data pointers.
So for instance for the Compact model (small code, large data):
sizeof(void *) > sizeof(void(*)())
and conversely in the Medium model (large code, small data):
sizeof(void *) < sizeof(void(*)())
In this case you didn't have separate storage for code and date but still couldn't convert between the two pointers (short of using non-standard __near and __far modifiers).
Additionally there's no guarantee that even if the pointers are the same size, that they point to the same thing - in the DOS Small memory model, both code and data used near pointers, but they pointed to different segments. So converting a function pointer to a data pointer wouldn't give you a pointer that had any relationship to the function at all, and hence there was no use for such a conversion.
Pointers to void are supposed to be able to accommodate a pointer to any kind of data -- but not necessarily a pointer to a function. Some systems have different requirements for pointers to functions than pointers to data (e.g, there are DSPs with different addressing for data vs. code, medium model on MS-DOS used 32-bit pointers for code but only 16-bit pointers for data).
In addition to what is already said here, it is interesting to look at POSIX dlsym():
The ISO C standard does not require that pointers to functions can be cast back and forth to pointers to data. Indeed, the ISO C standard does not require that an object of type void * can hold a pointer to a function. Implementations supporting the XSI extension, however, do require that an object of type void * can hold a pointer to a function. The result of converting a pointer to a function into a pointer to another data type (except void *) is still undefined, however. Note that compilers conforming to the ISO C standard are required to generate a warning if a conversion from a void * pointer to a function pointer is attempted as in:
fptr = (int (*)(int))dlsym(handle, "my_function");
Due to the problem noted here, a future version may either add a new function to return function pointers, or the current interface may be deprecated in favor of two new functions: one that returns data pointers and the other that returns function pointers.
C++11 has a solution to the long-standing mismatch between C/C++ and POSIX with regard to dlsym(). One can use reinterpret_cast to convert a function pointer to/from a data pointer so long as the implementation supports this feature.
From the standard, 5.2.10 para. 8, "converting a function pointer to an object pointer type or vice versa is conditionally-supported." 1.3.5 defines "conditionally-supported" as a "program construct that an implementation is not required to support".
Depending on the target architecture, code and data may be stored in fundamentally incompatible, physically distinct areas of memory.
undefined doesn't necessarily mean not allowed, it can mean that the compiler implementor has more freedom to do it how they want.
For instance it may not be possible on some architectures - undefined allows them to still have a conforming 'C' library even if you can't do this.
Another solution:
Assuming POSIX guarantees function and data pointers to have the same size and representation (I can't find the text for this, but the example OP cited suggests they at least intended to make this requirement), the following should work:
double (*cosine)(double);
void *tmp;
handle = dlopen("libm.so", RTLD_LAZY);
tmp = dlsym(handle, "cos");
memcpy(&cosine, &tmp, sizeof cosine);
This avoids violating the aliasing rules by going through the char [] representation, which is allowed to alias all types.
Yet another approach:
union {
double (*fptr)(double);
void *dptr;
} u;
u.dptr = dlsym(handle, "cos");
cosine = u.fptr;
But I would recommend the memcpy approach if you want absolutely 100% correct C.
They can be different types with different space requirements. Assigning to one can irreversibly slice the value of the pointer so that assigning back results in something different.
I believe they can be different types because the standard doesn't want to limit possible implementations that save space when it's not needed or when the size could cause the CPU to have to do extra crap to use it, etc...
The only truly portable solution is not to use dlsym for functions, and instead use dlsym to obtain a pointer to data that contains function pointers. For example, in your library:
struct module foo_module = {
.create = create_func,
.destroy = destroy_func,
.write = write_func,
/* ... */
};
and then in your application:
struct module *foo = dlsym(handle, "foo_module");
foo->create(/*...*/);
/* ... */
Incidentally, this is good design practice anyway, and makes it easy to support both dynamic loading via dlopen and static linking all modules on systems that don't support dynamic linking, or where the user/system integrator does not want to use dynamic linking.
A modern example of where function pointers can differ in size from data pointers: C++ class member function pointers
Directly quoted from https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/
class Base1 { int b1; void Base1Method(); };
class Base2 { int b2; void Base2Method(); };
class Derived : public Base1, Base2 { int d; void DerivedMethod(); };
There are now two possible this pointers.
A pointer to a member function of Base1 can be used as a pointer to a
member function of Derived, since they both use the same this
pointer. But a pointer to a member function of Base2 cannot be used
as-is as a pointer to a member function of Derived, since the this
pointer needs to be adjusted.
There are many ways of solving this. Here's how the Visual Studio
compiler decides to handle it:
A pointer to a member function of a multiply-inherited class is really
a structure.
[Address of function]
[Adjustor]
The size of a pointer-to-member-function of a class that uses multiple inheritance is the size of a pointer plus the size of a size_t.
tl;dr: When using multiple inheritance, a pointer to a member function may (depending on compiler, version, architecture, etc) actually be stored as
struct {
void * func;
size_t offset;
}
which is obviously larger than a void *.
On most architectures, pointers to all normal data types have the same representation, so casting between data pointer types is a no-op.
However, it's conceivable that function pointers might require a different representation, perhaps they're larger than other pointers. If void* could hold function pointers, this would mean that void*'s representation would have to be the larger size. And all casts of data pointers to/from void* would have to perform this extra copy.
As someone mentioned, if you need this you can achieve it using a union. But most uses of void* are just for data, so it would be onerous to increase all their memory use just in case a function pointer needs to be stored.
I know that this hasn't been commented on since 2012, but I thought it would be useful to add that I do know an architecture that has very incompatible pointers for data and functions since a call on that architecture checks privilege and carries extra information. No amount of casting will help. It's The Mill.

Does a union of a pointer and an array of pointers guarantee that the single pointer has the same address as the first element of the array?

With the struct:
struct stree {
union {
void *ob;
struct stree *strees[256];
};
};
Do the C standards (particularly C11) guarantee that ob is an alias of stree[0]?
Maybe. The C standard only guarantees the following, 6.3.2.3:
A pointer to void may be converted to or from a pointer to any object type. A pointer to
any object type may be converted to a pointer to void and back again; the result shall
compare equal to the original pointer.
In theory/formally, a void* doesn't need to have the same memory representation as a pointer to object. Whenever it encounters such a conversion, it could in theory do some tricks like keeping track of the original value, to restore it later.
In practice, void* and object pointers have the same size and format on any sane system, because that's by far the easiest way to sate the above requirement. Systems with extended addressing modes for objects exist, but they don't work as the C standard intended. Instead they use non-standard extensions by inventing near and far pointer qualifiers, which could then be applied either to void* or an object pointer. (Some examples of this are low-end microcontrollers and old MS DOS.)
Bottom line: designing for portability to insane or fictional systems is a huge waste of time. Toss in a static_assert(sizeof(void*) == sizeof(int*), "...") and that should be enough. Because in practice you're not going to find a system with magical or mysterious void* format. If you do, deal with it then.
What you have to keep in mind though, is that void* in itself is a different type, so you can't generally access the memory where the void* is stored through another pointer type (strict pointer aliasing). But there are a few exceptions to the strict aliasing rule, type punning through unions is one of them.
So regarding your union, the void* and the first object pointer are guaranteed to be allocated starting at the same address, but again their respective sizes an internal formats could in theory be different. In practice you can assume that they are the same, and because you use a union you aren't breaking strict aliasing.
As for best practice, it is to avoid void* in the first place, as there's very few places where you actually need them. For the few cases where you need to pass some generic pointer around, it is often better to use uintptr_t and store the result as an integer.

Is the alignment requirement for incomplete `struct X` and `struct Y` the same?

An answer to "C: When is casting between pointer types not undefined behavior?" indicates that casting forth and back via a pointer with no stricter alignment requirement is safe.
The alignment is important here even if the types are incomplete as the standard states:
A pointer to an object or incomplete type may be converted to a pointer to a different
object or incomplete type. If the resulting pointer is not correctly aligned
for the
pointed-to type, the behavior is undefined. Otherwise, when converted back again, the
result shall compare equal to the original pointer. When a pointer to an object is
converted to a pointer to a character type, the result points to the lowest addressed byte of
the object. Successive increments of the result, up to the size of the object, yield pointers
to the remaining bytes of the object.
Now the question is what correct alignment requirement of struct X is. Of course it would depend on it's contents if it is complete, but what if it's an incomplete (that it is only declared as struct X;)? Or put another way, can we cast from struct X* to struct Y* and back and get the same pointer again?
If the alignment requirement of a complete struct X and incomplete differs would that be a complication? I can't come up with an actual conversion between them, but it could be that you pass a struct X* between translation between context where the struct X is complete respectively incomplete. Or is this a special case that's always allowed anyway?
To start with, I don't think you can talk about the alignment requirement of an incomplete type, because alignment requirements are only defined for complete types:
Complete object types have alignment requirements which place restrictions on the addresses at which objects of that type may be allocated. (§6.2.8/1; all standard citations are taken from n1570.pdf, which is effectively C11)
But in a valid program, a pointer must point to an object of complete type (or NULL). So even if the referenced type of a pointer is incomplete in some translation unit, the object referred to by that pointer (if there is one) must have a complete type and thus an alignment requirement. However, in the translation unit in which the referred type is incomplete, there is no way to know what that alignment requirement is, neither for the compiler nor for the person writing the code.
So the only way a pointer to an incomplete type can be legitimately assigned a non-NULL value is by means of a copy of a pointer to the same type from somewhere where the type is complete (perhaps in another translation unit). This is not a conversion, since the two types are compatible, and it is guaranteed to work. Similarly, one of the few legitimate uses for a value of a pointer to an incomplete type is to pass it to a pointer to the same type in a context where the type is complete. (Other uses include comparing it for equality with another pointer value or converting it to an integer and printing it out, but these don't involve converting to a different pointer type.)
So, in summary, pointers to incomplete types are useful and usable precisely in the expected use case -- where the referenced type is opaque -- but cannot be portably converted to pointers of any other type except for possibly qualified char* and void*.
The standard does not guarantee the possibility of converting a pointer to a pointer to a type whose alignment might be more stringent. If the alignment of the referred type is unknown, the only pointers whose target alignment cannot be more stringent are char* and void*. So any conversion of a pointer to an incomplete type to a type other than char* or void* must be regarded as unportable.
Really, the fact that the referred type is incomplete is not hugely relevant. The standard does not specify the alignment of a composite type. Such an alignment must be sufficient to allow the compiler to correctly align members, but it could be arbitrarily large. In other words, there is no guarantee that the types:
typedef char oneChar;
struct oneChar_s { char x; };
union oneChar_u { char x; };
have the same alignment. It is quite possible for the two composite types to have larger alignments than 1. So there is no guarantee that it is possible to convert a oneChar* to a oneChar_s* (unless, of course, the oneChar* were the result of a previous conversion in the opposite direction), and a portable program would not try. In this sense, it makes no difference whether the definition of struct oneChar_s is visible or not.
Not coincidentally, the standard does not guarantee that all object pointers have the same size. The underlying theory is that on some architectures, ordinary pointers are not sufficiently precise to refer to single bytes, but there is the possibility of augmenting the pointer with the addition of, for example, a bit offset. Indeed, it might be the case that there are other small objects which can be packed into words, which also require augmented pointer representations but with less precision than a bit offset.
In such an architecture, it is not possible to take advantage of different pointer precisions for small composite objects, because the standard insists that there be at most two representation of pointers to composites, one for structs and one for unions:
All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. (§6.2.5/27) [Note 2]
In particular, this means that pointers to objects of a given type have the same representation, regardless of whether the type is complete.
Difference in pointer representations is not the only reason that a conversion to a more constrained alignment might fail. For example, an implementation might insert code to verify alignment after a conversion (perhaps in response to a sanitizing compiler option). In the case of an incomplete type, the compiler would not be able to insert static code to do the check (although some kind of runtime check might be possible), but the fact that the compiler might omit the validation code would not alter the undefinedness of the result.
For what its worth, the standard citation in the OP was for C99; in C11, it has been modified slightly (emphasis added to indicate changed wording):
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object. (§6.3.2.3/7)
In my opinion, this change is purely editorial. It derives from the decision to change the definition of "object type" in §6.2.5/1. In C99, there were three kinds of type: object types, function types, and incomplete types. In C11, there are only two kinds -- object types and function types -- with the comment that "At various points within a translation unit an object type may be incomplete… or complete…", which is a more accurate description.
Notes
As a completely hypothetical example, consider a machine conceptually similar to the PDP-6/10 architecture. This is a word-addressed machine with a large word size; a word is large enough to contain two addresses (a fact which a hypothetical LISP implementation could take advantage of in order to store a cons node consisting of car and cdr fields into a single word). Because it is desired to represent vectors of small objects efficiently, the machine also has instructions which can extract or overwrite a bitfield within a word, where the bitfield pointer consists of a word pointer accompanied by an offset and length information. (Hence, a word can contain only one bitfield pointer.) (The hardward has an instruction which can increment bitfield pointers by adding the length to the offset and moving to bitfield starting at 0 of the next word if necessary.)
So there could be three different pointer types:
a fullword character pointer consisting of an address and a bitfield offset/length.
an ordinary halfword pointer to any word-aligned object type.
an augmented halfword-plus-one-bit pointer to a pointer to a word aligned object, consisting of an address and an indication of whether the address is in the first or second half of the word. (This representation also probably requires a fullword, but the encoding is simpler. But there might also be some other place for the extra bit. It's a hypothetical example, remember.)
In this hypothetical architecture, the conversion rules become quite complicated. For example, you can convert an char** to an int* because the alignment of int is the same as the alignment of char*. But you cannot convert an int** to an int* because the alignment of int is greater than the alignment of int*.
Rather than memorizing these complex rules, a programmer would probably choose to simply forbear from performing pointer conversions other than those guaranteed to be portable, which is round-tripping through char* or void*.
It would be possible for pointers to all composites to use the larger, more precise pointer type, even if unnecessarily. It seems to me much more likely that an implementation would simply choose to impose a minimum alignment on structs, if not all composite objects. The wording of the standard would allow an implementation to use a minimum alignment for structs and an augmented pointer representation for all unions.

void* will have the same representation and memory alignment as a pointer to char

I am reading a book on pointers named "understanding and using c pointers"
When it comes to void * it says
It has two interesting properties:
A pointer to void will have the same representation and memory alignment as a pointer to char.
What am confused about is isn't the memory of all the pointers same? They why instead of writing void* is same as normal pointer it explicitly mentioned char pointers? Will really appreciate any help
On most common architectures, pointer to any data type has the same representation, while pointer to function may differ. However, it's not a requirement, so it's possible to create valid C implementation, which uses different pointers for different data types. The reason behind this is that C standard tends to describe only crucial requirements, leaving a lot of freedom for possible implementations. Here is what standard says:
A pointer to void shall have the same representation and alignment requirements as a
pointer to a character type.
Similarly, pointers to qualified or unqualified versions of
compatible types shall have the same representation and alignment requirements. All
pointers to structure types shall have the same representation and alignment requirements
as each other. All pointers to union types shall have the same representation and
alignment requirements as each other. Pointers to other types need not have the same
representation or alignment requirements.
If you're curious to see examples of systems with different sizes for different data types, this question mentions these wonderful examples
I think the point here is "memory alignment", not "memory size".
Yes, all pointers have same size of memory. But they may have different limitation for memory alignment.
For example, on some platforms, a "32-bit int" pointer must point to the address which should be times of 4 bytes. It cannot point to, e.g. 0x100001 or 0x100003.
But a "8-bit char" pointer can point to any address. So does a "void" pointer.
So it said that.

Pointers in C: Data type or operation

Would we call the pointers in C a data type (of type void*) or just an operation done on an unsigned integer which makes its value get interpreted as a virtual memory address ? If it is a data type, which hardware and software factors impact its range ?
I think you are mixing two things together - Indirection operator and pointer variables.
Indirection operator (*) - The indirection operator is a unary operator that can be used to obtain the value stored at the memory location referenced by a pointer variable.
pointer variables - In C pointers are variables of specific type (e.g. int,char, void) that store addresses and can be null.
The size of pointers depends upon different factors. You can go to below link. They have explained it well.
What is the size of a pointer? What exactly does it depend on?
C thinks your whole computer is one massive array of bytes. Obviously this isn't very useful, but then C layers on top of this massive array of bytes the concept of types and sizes of those types.
Creating a block of memory inside your computer.
"Pointing" the name ptr at the beginning of that block().
This indirectly means it needs a type specifier which will be same as the data type it is pointing to.
Yes they are data types. Like void *.
intptr_t integers can store any pointers except function pointers. So you can think of such pointers to data objects as special integers. (But remember integer and pointer are different types.)
Your compiler generates code for your target environment. E.g. if you compile with the option -m32 then pointer size is probably 32 bits. It can be run on 32-bit architecture where the CPU register can store 32-bit memory address. Although 64-bit architecture may still support 32-bit application, you can use the option -m64 for compilation to benefit from bigger pointers (probably 64 bits).
You're asking about a lot of ideas. I'm going to try and cite the C standard as much as possible to give the official idea, then break it down into something digestible.
Would we call the pointers in C a data type (of type void *) or just an operation done on an unsigned integer which makes its value get interpreted as a virtual memory address?
From the C standard:
6.2.5 Types
A pointer type may be derived from a function type, an object type, or an incomplete type, called the referenced type. A pointer type describes an object whose value provides a reference to an entity of the referenced type. A pointer type derived from the referenced type T is sometimes called ‘‘pointer to T’’. The construction of a pointer type from a referenced type is called ‘‘pointer type derivation’’.
6.3.2.3 Pointers
An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.
Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.
A pointer isn't a data type (at least in the traditional sense of the phrase) or an integer - it's a C programmer's way of denoting the location of a certain object. While we tend to think of that as being a number - i.e., 0xf000b3a8 - there's no reason it has to be a number. It just has to mark the location of a particular object. Furthermore, the type of the object it points to dictates its own pointer type. So a pointer with type of "float *" is equivalent to "pointer to float". It is not a float type - it's a pointer type.
For a concrete example of this, think about how you organize your bedroom - perhaps you have a bed, a desk, a set of drawers, a closet, and a bedside table. Before you go to bed, you pull out a sticky note and write "Remember headphones on desk". You then put that sticky note on your backpack. That sticky note is a pointer to your headphones! It represents a reminder to you of where those headphones are located - your desk, not a number.
The reason we tend to think of pointers as integers is from sections 6.3.2.3.5 and 6.3.2.3.6. Computers - as they're currently designed - don't understand the concept of "a desk" or "a closet". They only understand numbers - so we organize them accordingly. As a result, the C standard dictates that integers must be able to be converted into pointers, and vice-versa.
If it is a data type, which hardware and software factors impact its range?
Again, a pointer isn't really a data type - it denotes the location of a certain object (that in turn has a certain type that dictates the pointer's type). So we can't really talk about the range of a pointer, per se. What we can talk about is the range of possible memory addresses (and thus the range of possible locations that a pointer could point to!).
Back in the day, when dinosaurs roamed the earth and Nixon was in office (I'm a young'un, what can I say), Intel came out with the Intel 8008 microprocessor1. This puppy was the world's first 8-bit CPU - that is, it could perform mathematical operations on 8-bit values. Assuming 2's complement notation, this allows unsigned values of 0 to 28-1 (0 to 255) and signed values of -27 to 27-1 (-128 to 127). Furthermore, it had an external 14-bit address bus that could address up to 16KB of memory. This equates to 214 (16384) memory locations, each containing 8 bits. The full range is thus from byte 0 to byte 16383.
Fast-forward to today. Most modern desktop and laptop PCs run on 64-bit processors. These support (theoretically) 264 bytes of memory - a whopping 18,446,744,073,709,551,616 locations. This equates to approximately 16 exabytes - where one exabyte is 1,000,000 terabytes. (It's speculated that Google stores approximately this much data in their datacenters). Currently, however, most architectures only use the lower 48-bits2.
These represent the total number of physical locations that a pointer in C could theoretically point to. In reality, because of how virtual memory works, the range that you as a programmer will see is far more limited. That's a story for another day, however.
A pointer is a variable whose value is the address of another variable. Generally speaking, a pointer in C can point to any address within the virtual memory, and NULL.
A pointer is generally a variable whose value is the address of another variable, e.g. direct address of the memory location. Like any variable or constant, you must declare a pointer before you can use it to store any variable address.
The actual data type of the value of all pointers, whether integer, float, character, or otherwise, is the same, a long hexadecimal number that represents a memory address.
e.g: int *ip; /* pointer to an integer */
The run-time representation of a pointer value is typically a raw memory address (perhaps augmented by an offset-within-word field), but since a pointer's type includes the type of the thing pointed to, expressions including pointers can be type-checked at compile time. Pointer arithmetic is automatically scaled by the size of the pointed-to data type.
AS for void pointers (void *) point to objects of unspecified type, and can therefore be used as "generic" data pointers. Since the size and type of the pointed-to object is not known, void pointers cannot be dereferenced, nor is pointer arithmetic on them allowed, although they can easily be (and in many contexts implicitly are) converted to and from any other object pointer type.
See wiki

Resources