30

I know that pointers hold addresses. I know that pointers' types are "generally" known based on the "type" of data they point to. But, pointers are still variables and the addresses they hold must have a data "type". According to my info, the addresses are in hexadecimal format. But, I still do not know what "type" of data is this hexadecimal. (Note that I know what a hexadecimal is, but when you say 10CBA20, for example, is this string of characters? integers? what? When I want to access the address and manipulate it .. itself, I need to know its type. This is why I am asking.)

  • 17
    Pointers are not variables, but values. Variables hold values (and if their type is a pointer type, that value is a pointer, and might be the address of a memory zone containing something meaningful). A given memory zone could be used to hold various values of different types. – Basile Starynkevitch Jun 9 '15 at 8:30
  • 29
    "the addresses are in hexadecimal format" No, that's just the debugger or a library formatting bits. With the same argument you could say they are in binary or octal. – usr Jun 9 '15 at 9:48
  • You'd be better off asking about the format, not the type. Hence some off-piste answers below... (though Kilian's is spot-on). – Lightness Races with Monica Jun 9 '15 at 18:01
  • 1
    I think the deeper issue here is OP's understanding of type. When it comes down to it, the values you're manipulating in your program are just bits in memory. Types are the programmer's way of telling the compiler how to treat those bits when it generates assembly code. – Justin Lardinois Jun 10 '15 at 6:40
  • I suppose it's too late to edit it with all those answers, but this question would have been better if you had restricted the hardware and/or operating system, for example "on x64 Linux". – hyde Jun 10 '15 at 7:34

10 Answers 10

64

The type of a pointer variable is .. pointer.

The operations you're formally allowed to do in C are to compare it (to other pointers, or the special NULL / zero value), to add or subtract integers, or to cast it to other pointers.

Once you accept undefined behaviour, you can look at what the value actually is. It will usually be a machine word, the same kind of thing as an integer, and can usually be losslessly cast to and from an integer type. (Quite a lot of Windows code does this by hiding pointers in DWORD or HANDLE typedefs).

There are some architectures where pointers are not simple because memory is not flat. DOS/8086 'near' and 'far'; PIC's different memory and code spaces.

  • 2
    You're also permitted to take the difference between two pointers p1-p2. The result is a signed integral value. In particular, &(array[i])-&(array[j]) == i-j – MSalters Jun 9 '15 at 11:35
  • 13
    Actually, conversion to an integral type is also specified, specifically to intptr_t and uintptr_t which are guaranteed to be "large enough" for pointer values. – Matthieu M. Jun 9 '15 at 13:16
  • 3
    You can depend on the conversion to work, but the mapping between integers and pointers is implementation-defined. (The sole exception is 0 -> null, and even that is only specified if the 0 is a constant IIRC.) – cHao Jun 9 '15 at 13:58
  • 7
    The addition of the p specifier to printf makes getting a human-readable representation of a void pointer a defined, if implementation dependent behavior in c. – dmckee Jun 9 '15 at 14:46
  • 6
    This answer has the generally right idea, but fails on the specific claims. Coercing a pointer to integral type is not undefined behavior, and Windows HANDLE data types are not pointer values (they are not pointers hidden in integral data types, they are integers hidden in pointer types, to prevent arithmetic). – Ben Voigt Jun 9 '15 at 15:51
44

You're overcomplicating things.

Addresses are just integers, period. Ideally they're the number of the referenced memory cell (in practice this gets more complicated because of segments, virtual memory etc.).

Hexadecimal syntax is a complete fiction that exists only for the convenience of programmers. 0x1A and 26 are exactly the same number of exactly the same type, and neither is what the computer uses - internally, the computer always uses 00011010 (a series of binary signals).

Whether or not a compiler allows you to treat pointers as numbers depends on the language definition - "systems programming" languages are traditionally more transparent about how things work under the hood, while "high-level" languages more often try to hide the bare metal from the programmer - but that changes nothing about the fact that pointers are numbers, and usually the most common type of number (the one with as many bits as your processor architecture).

  • 26
    Addresses are most definitely not just integers. Just as floating-point numbers are most definitely not just integers. – gnasher729 Jun 9 '15 at 11:17
  • 8
    Indeed. The best known counterexample is the Intel 8086, where pointers are two integers. – MSalters Jun 9 '15 at 11:32
  • 5
    @Rob In a segmented memory model, a pointer can be either a single value (an address relative to the start of the segment; an offset) with the segment implied, or a segment/selector and offset pair. (I think Intel used the term "selector"; I'm too lazy to look it up.) On the 8086, these were represented as two 16-bit integers, which combined to form one 20-bit physical address. (Yes, you could address the same memory cell in many, many different ways, if you were so inclined: address=(segment<<4 + offset) & 0xfffff.) This carried forward through all x86 compatibles when running in real mode. – a CVn Jun 9 '15 at 12:09
  • 4
    As a long-term assembler programmer, I can attest that the computer's memory is nothing but memory locations holding integers. However, it's how you treat them and keeping track of what those integers represent that's important. E.g. On my system, the decimal number 4075876853 is stored as x'F2F0F1F5', which is the string '2015' in EBCDIC. Decimal 2015 would be stored as 000007DF whereas x'0002015C' represents decimal 2015 in packed-decimal format. As an assembler programmer, you have to keep track of these; the compiler does it for HL languages. – Steve Ives Jun 9 '15 at 16:01
  • 7
    Addresses can be put into one-to-one correspondence with integers, but so can everything else on a computer :) – hobbs Jun 9 '15 at 21:46
16

A pointer is just that - a pointer. It's not something else. Don't try to think that it is something else.

In languages like C, C++, and Objective-C, data pointers have four kinds of possible values:

  1. A pointer can be the address of an object.
  2. A pointer can point just past the last element of an array.
  3. A pointer can be a null pointer, which means it isn't pointing to anything.
  4. A pointer can have an indeterminate value, in other words it is rubbish, and anything can happen (including bad things) if you try to use it.

There are also function pointers, which either identify a function, or are null function pointers, or have an indeterminate value.

Other pointers are "pointer to member" in C++. These are most definitely not memory addresses! Instead, they identify a member of any instance of a class. In Objective-C, you have selectors, which are something like "pointer to an instance method with a given method name and argument names". Like a member pointer, it identifies all methods of all classes as long as they look the same.

You can investigate how a specific compiler implements pointers, but that is an entirely different question.

  • 4
    There are pointers to functions and, in C++, pointers to members. – sdenham Jun 9 '15 at 12:55
  • C++ pointers to members aren't memory addresses? Sure they are. class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi; The variable pmi wouldn't be much use if it didn't contain a memory address, namely, as the last line of the code establishes, the address of member num of instance a of class A. You could cast this to an ordinary int pointer (although the compiler would probably give you a warning) and dereference it successfully (proving that it is syntactic sugar for any other pointer). – dodgethesteamroller Jun 10 '15 at 6:46
9

A pointer is a bit pattern addressing (uniquely identifying for the purpose of reading or writing) a word of storage in RAM. For historical and conventional reasons the unit of update is eight bits, known in English as a "byte" or in French, rather more logically, as an Octet. This is ubiquitous but not inherent; other sizes have existed.

If I remember correctly there was one computer that used a 29bit word; not only is this not a power of two, it's even prime. I thought this was SILLIAC but the pertinent Wikipedia article doesn't support this. CAN BUS uses 29 bit addresses but by convention network addresses are not referred to as pointers even when they are functionally identical.

People keep asserting that pointers are integers. This is neither intrinsic nor essential, but if we interpret the bit patterns as integers the useful quality of ordinality emerges, enabling very direct (and therefore efficient on small hardware) implementation of constructs like "string" and "array". The notion of contiguous memory depends on ordinal adjacency, and relative positioning is possible; integer comparison and arithmetic operations can be meaningfully applied. For this reason there is nearly always strong correlation between the word size for storage addressing and the ALU (the thing that does integer math).

Sometimes the two don't correspond. In early PCs the address bus was 24 bits wide.

  • Nitpick, these days in common OSes, pointer identifies location in virtual memory, and has nothing directly to do with a physical RAM word (virtual memory location might not even exist physically if it is in a memory page known to be all zeroes by the OS). – hyde Jun 9 '15 at 19:21
  • @hyde - Your argument has merit in the context in which you obviously intended it, but the dominant form of computer is the embedded controller, where marvels like virtual memory are recent innovations with limited deployment. Also, what you have pointed out in no way helps the OP to understand pointers. I thought some historical context would make it all a lot less arbitrary. – Peter Wone Jun 9 '15 at 21:48
  • I don't know if talking about RAM will help OP to understand, as the question is specifically about what pointers really are. Oh, another nitpick, in c pointer by definition points to byte (can be safely cast to char* eg. for memory copying/comparing purposes, and sizeof char==1 as defined by C standard), not word (unless CPU word size is same as byte size). – hyde Jun 10 '15 at 4:22
  • What pointers fundamentally are is hash keys for storage. This is language and platform invariant. – Peter Wone Jun 10 '15 at 6:09
  • The question is about c pointers. And pointers definitely aren't hash keys, because there's no hash table, no hashing algorithm. They naturally are some kind of map/dictionary keys (for sufficiently broad definition of "map"), but not hash keys. – hyde Jun 10 '15 at 7:29
6

Basically every modern computer is a bit-pushing machine. Usually it pushes bits around in clusters of data, called bytes, words, dwords or qwords.

A byte consists of 8 bits, a word 2 bytes (or 16 bits), a dword 2 words (or 32 bits) and a qword 2 dwords (or 64 bits). These are not the only way to arrange bits. 128 bit and 256 bit manipulation also occurs, often in SIMD instructions.

Assembly instructions operate on registers and memory addresses usually operate in one of the above forms.

ALU (arithmetic logic units) operate on such bundles of bits as if they represented integers (usually Two's Complement format), and FPUs as if they where floating point values (usually IEEE 754-style float and double). Other parts will act as if they are bundled data of some format, characters, table entries, CPU instructions, or addresses.

On a typical 64 bit computer, bundles of 8 bytes (64 bits) are addresses. We display these addresses conventionally as in a hex format (like 0xabcd1234cdef5678), but that is just an easy way for humans to read the bit patterns. Each byte (8 bits) is written as two hex characters (equivalently each hex character -- 0 to F -- represents 4 bits).

What is actually going on (for some level of actually) is that there are bits, usually stored in a register or stored in adjacent locations in a memory bank, and we are just trying to describe them to another human.

Following a pointer consists of asking the memory controller to give us some data at that location. You'd typically ask the memory controller for a certain number of bytes at a certain location (well, implicitly a range of locations, usually contiguous), and it is delivered through various mechanisms I won't get into.

The code usually specifies a destination for the data to be fetched to -- a register, another memory address, etc -- and usually it is a bad idea to load floating point data into a register expecting an integer, or vice versa.

The type of the data in C/C++ is something that the compiler keeps track of, and it changes what code is generated. Usually there is nothing intrinsic in the data that makes it actually of any one type. Just a collection of bits (arranged into bytes) that are manipulated in a integer-like way (or a float-like way, or an address-like way) by the code.

There are exceptions to this. There are architectures where certain things are a different kind of bits. The most common example is protected execution pages -- while instructions telling the CPU what do do are bits, at run time the (memory) pages containing code to execute are marked specially, cannot be modified, and you cannot execute pages that are not marked as execution pages.

There are also read only data (sometimes stored in ROM that cannot physically be written to!), alignment issues (some processors cannot load doubles from memory unless they are aligned in particular ways, or SIMD instructions that require certain alignment), and myriads of other architecture quirks.

Even the above level of detail is a lie. Computers aren't "really" pushing around bits, they are really pushing around voltages and current. These voltages and current sometimes don't do what they are "supposed" to do at the abstraction level of bits. The chips are designed to detect most such errors and correct them without the higher level abstraction having to be aware of it.

Even that is a lie.

Each level of abstraction hides the one below, and lets you think about solving problems without having to keep in mind Feynman diagrams in order to print out "Hello World".

So at a sufficient level of honesty, computers push bits, and those bits are given meaning by how they are used.

3

People have made a big deal of whether pointers are integers or not. There are actually answers to these questions. However, you are going to have to take a step into the land of specifications, which is not for the faint of heart. We're going to take a look at the C specification, ISO/IEC 9899:TC2

6.3.2.3 Pointers

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

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

Now for this, you're going to need to know some common spec terms. "implementation defined" means every single compiler is allowed to define it differently. In fact, a compiler may even define it different ways depending on yours compiler settings. Undefined behavior means the compiler is allowed to do absolutely anything at all, from giving a compile time error to unexplainable behaviors, to working just perfectly.

From this we can see that the underlying storage form is not specified, other than that there may be a conversion to an integer type. Now truth be told, virtually every compiler under the sun represents pointers under the hood as integer addresses (with a handful of special cases where it might be represented as 2 integers instead of just 1), but the specification permits absolutely anything, such as representing addresses as a 10 character string!

If we fast forward out of C and look at the C++ spec, we get a little more clarity with reinterpret_cast, but this is a different language, so its value for you may vary:

ISO/IEC N337: C++11 draft specification (I only have the draft on hand)

5.2.10 Reinterpret cast

  1. A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is implementation-defined. [Note: It is intended to be unsurprising to those who know the addressing structure of the underlying machine. —end note] A value of type std::nullptr_t can be converted to an integral type; the conversion has the same meaning and validity as a conversion of (void*)0 to the integral type. [Note: A reinterpret_cast cannot be used to convert a value of any type to the type std::nullptr_t . —end note]

  2. A value of integral type or enumeration type can be explicitly converted to a pointer. A pointer converted to an integer of sufficient size (if any such exists on the implementation) and back to the same pointer type will have its original value; mappings between pointers and integers are otherwise implementation-defined. [Note: Except as described in 3.7.4.3 , the result of such a conversion will not be a safely-derived pointer value. —end note ]

As you can see here, with a few more years under its belt, C++ found that it was safe to assume that a mapping to integers existed, so there is no longer talk of undefined behavior (though there is an interesting contradiction between parts 4 and 5 with the phrasing "if any such exists on the implementation")


Now what should you take away from this?

  • The exact representation of pointers is implementation defined. (in fact, just to make it messier, some small embedded computers represent the null pointer, (void)0, as address 255 to support some address aliasing tricks they use)*
  • If you have to ask about the representation of pointers in memory, you are probably not at a point in your programming career where you want to be fiddling with them.

The best bet: casting to a (char*). The C and C++ specifications are full of rules specifying the packing of arrays and structs, and both always allow the casting of any pointer to a char*. char is always 1 byte (not guaranteed in C, but by C++11 it has become a mandated part of the language, so its relatively safe to assume it is 1 byte everywhere). This allows you to do some pointer arithmetic at the byte-by-byte level without resorting to actually needing to know the implementation specific representations of pointers.

  • Can you necessarily cast a function pointer to a char *? I'm thinking of a hypothetical machine with separate address spaces for code and data. – Philip Kendall Jun 10 '15 at 5:54
  • @PhilipKendall Good point. I did not include that part of the spec, but function pointers are treated as a completely different thing than data pointers in the specification because of exactly the issue you raise. Member pointers are also treated differently (but they also act very differently as well) – Cort Ammon - Reinstate Monica Jun 10 '15 at 15:07
  • A char is always 1 byte in C. Quoting from the C standard: "The sizeof operator yields the size (in bytes) of its operand" and "When sizeof is applied to an operand that has type char, unsigned char, or signed char, (or a qualified version thereof) the result is 1." Perhaps you are thinking a byte is 8 bits long. That is not necessarily the case. To be compliant with the standard, a byte must contain at least 8 bits. – David Hammen Jun 11 '15 at 0:35
  • The spec describes converting between pointer and integer types. It should always be kept in mind that a "conversion" between types doesn't imply an equality of the types, nor even that a binary representation of the two types in memory would have the same bit pattern. (ASCII can be "converted" to EBCDIC. Big-endian can be "converted" to little-endian. Etc.) – user2338816 Jun 11 '15 at 6:44
1

On most architectures, the type of a pointer ceases to exist once they've been translated into machine code (except for perhaps "fat pointers"). Therefore, a pointer to an int would be indistinguishable from a pointer to a double, at least on its own.*

[*] Although, you can still make guesses based on the kinds of operations that you apply to it.

1

An important thing to understand about C and C++ is what types actually are. All they really do is indicate to the compiler how to interpret a set of bits/bytes. Lets start with the following code:

int var = -1337;

Depending on architecture, an integer is usually given 32 bits of space to store that value. That means that the space in memory where var is stored will look something like "11111111 11111111 11111010 11000111" or in hex "0xFFFFFAC7". That's it. That is all that is stored at that location. All types do is tell the compiler how to interpret that information. Pointers are no different. If I do something like this:

int* var_ptr = &var;   //the ampersand is telling C "get the address where var's value is located"

Then the compiler will get the location of var, and then store that address in the same way the first code snippet saves the value -1337. There is no difference in how they are stored, just in how they are used. It doesn't even matter that I made var_ptr a pointer to an int. If you wanted to, you could do.

unsigned int var2 = *(unsigned int*)var_ptr;

This will copy the above hex value of var (0xFFFFFAC7) into the location that stores var2's value. If we were to then use var2, we'd find that the value would be 4294965959. The bytes in var2 are the same as var, but the numerical value differs. The compiler interpreted them differently because we told it that those bits represent an unsigned long. You could do the same for the pointer value too.

unsigned int var3 = (unsigned int)var_ptr;

You'd end up interpreting the value that represents the address of var as an unsigned int in this example.

Hopefully this clarifies things for you and gives you a better insight as to how C works. Please note that you SHOULD NOT do any of the crazy stuff I did in the below two lines in actual production code. That was just for demonstration.

1

Integer.

Address space in a computer is numbered sequentially, starting at 0, and increments by 1. So a pointer will hold an integer number which corresponds to an address in address space.

1

Types combine.

In particular, certain types combine, almost as if they were parameterized with placeholders. Array and pointer types are like this; they have one such placeholder, which is the type of the element of the array or the thing being pointed to, respectively. Function types are also like this; they can have multiple placeholders for the parameters, and a placeholder for the return type.

A variable that is declared to hold a pointer to char has type "pointer to char". A variable that is declared to hold a pointer to pointer to int has type "pointer to pointer to int".

A (value of) type "pointer to pointer to int" can be changed to "pointer to int" by a dereference operation. So, the notion of type is not just words but a mathematically significant construct, dictating what we can do with values of the type (such as dereference, or pass as parameter or assign to variable; it also determines the size (byte count) of indexing, arithmetic, and increment/decrement operations).

P.S. If you want to get way deep into types, try this blog: http://www.goodmath.org/blog/2015/05/13/expressions-and-arity-part-1/

Not the answer you're looking for? Browse other questions tagged or ask your own question.