Also, far and near pointers ... can anyone elaborate a bit?
In C++, I have no clue on how pointers work in the direct opcode level, or on the circuit level, but I know it's memory accessing other memory, or vice-versa, etc.
But in Assembly you can use pointers as well.
Is there any notable difference here that's worth knowing, or is it the same concept? Is it applied differently on the mneumonics level of low-level microprocessor specific Assembly?
Near and far pointers were only relevant for 16 bit operating systems. Ignore them unless you really really need them. You might still find the keywords in today's compilers for backwards compatibility but they won't actually do anything. In 16-bit terms, a near pointer is a 16-bit offset where the memory segment is already known by the context and a far pointer contains both the 16-bit segment and a 16-bit offset.
In assembler a pointer simply points to a memory location. The same is true in C++, but in C++ the memory might contain an object; depending on the type of the pointer, the address may change, even though it's the same object. Consider the following:
class A
{
public:
int a;
};
class B
{
public:
int b;
};
class C : public A, B
{
public:
int c;
};
C obj;
A * pA = &obj;
B * pB = &obj;
pA and pB will not be equal! They will point to different parts of the C object; the compiler makes automatic adjustments when you cast the pointer from one type to another. Since it knows the internal layout of the class it can calculate the proper offsets and apply them.
Generally, pointer is something that allows you to access something else, because it points to it.
In a computer, the "something" and the "something else" are memory contents. Since memory is accessed by specifying its memory address, a pointer (something) is a memory location that stores the memory address of something else.
In a programming language, high level or assembler, you give memory addresses a name since a name is easier to remember than a memory address (that is usually given as a hex number). This name is the name of constant that for the compiler (high level) or the assembler (machine level) is exactly the same as the hex number, or, of a variable (a memory location) that stores the hex number.
So, there is no difference in the concept of a pointer for high level languages like C++ or low level languages like assembler.
Re near/far:
In the past, some platforms, notably 16-bit DOS and 16-bit Windows, used a concept of memory segments. Near pointers were pointer into an assumed default segment and were basically just an offset whereas far pointers contained both a segment part and an offset part and could thus represent any address in memory.
In DOS, there were a bunch of different memory models you could choose from for C/C++ in particular, one where there was just one data segment and so all data pointers were implicitly near, several where there was only one code segment, one where both code and data pointers were far, and so on.
Programming with segments is a real PITA.
In addition to what everyone said regarding near/far: the difference is that in C++, pointers are typed - the compiler knows what they are pointing at, and does some behind the scenes address arithmetics for you. For example, if you have an int *p and access p[i], the compiler adds 4*i to the value of p and accesses memory at that address. That's because an integer (in most modern OSes) is 4 bytes long. Same with pointers to structures/classes - the compiler will quietly calculate the offset of the data item within the structure and adjust the memory address accordingly.
With assembly memory access, no such luck. On assembly level, technically speaking, there's almost no notion of variable datatype. Specifically, there's practically no difference between integers and pointers. When you work with arrays of something bigger than a byte, keeping track of array item length is your responsibility.
Related
I have heard quite a lot about storing external data in pointer.
For example in (short string optimization).
For example:
when we want to overload << for our SSO class, dependant of the length of the string we want to print either value of pointer or string.
Instead of creating bool flag we could encode this flag inside pointer itself. If i am not mistaken its thanks PC architecture that adds padding to prevent unalligned memory access.
But i have yet to see it in example. How could we detect such flag, when binary operation such as & to check if RSB or LSB is set to 1 ( as a flag ) are not allowed on pointers? Also wouldnt this mess up dereferencing pointers?
All answers are appreciated.
It is quite possible to do such things (unlike other's have said). Most modern architectures (x86-64, for example) enforce alignment requirements that allow you to use the fact that the least significant bits of a pointer may be assumed to be zero, and make use of that storage for other purposes.
Let me pause for a second and say that what I'm about to describe is considered 'undefined behavior' by the C & C++ standard. You are going off-the-rails in a non-portable way by doing what I describe, but there are more standards governing the rules of a computer than the C++ standard (such as the processors assembly reference and architecture docs). Caveat emptor.
With the assumption that we're working on x86_64, let us say that you have a class/structure that starts with a pointer member:
struct foo {
bar * ptr;
/* other stuff */
};
By the x86 architectural constraints, that pointer in foo must be aligned on an 8-byte boundary. In this trivial example, you can assume that every pointer to a struct foo is therefore an address divisible by 8, meaning the lowest 3 bits of a foo * will be zero.
In order to take advantage of such a constraint, you must play some casting games to allow the pointer to be treated as a different type. There's a bunch of different ways of performing the casting, ranging from the old C method (not recommended) of casting it to and from a uintptr_t to cleaner methods of wrapping the pointer in a union. In order to access either the pointer or ancillary data, you need to logically 'and' the datum with a bitmask that zeros out the part of the datum you don't wish.
As an example of this explanation, I wrote an AVL tree a few years ago that sinks the balance book-keeping data into a pointer, and you can take a look at that example here: https://github.com/jschmerge/structures/blob/master/tree/avl_tree.h#L31 (everything you need to see is contained in the struct avl_tree_node at the line I referenced).
Swinging back to a topic you mentioned in your initial question... Short string optimization isn't implemented quite the same way. The implementations of it in Clang and GCC's standard libraries differ somewhat, but both boil down to using a union to overload a block of storage with either a pointer or an array of bytes, and play some clever tricks with the string's internal length field for differentiating whether the data is a pointer or local array. For more of the details, this blog post is rather good at explaining: https://shaharmike.com/cpp/std-string/
"encode this flag inside pointer itself"
No, you are not allowed to do this in either C or C++.
The behaviour on setting (let alone dereferencing) a pointer to memory you don't own is undefined in either language.
Sadly what you want to achieve is to be done at the assembler level, where the distinction between a pointer and integer is sufficiently blurred.
When you learn C++, or at least when I learned it through C++ Primer, pointers were termed the "memory addresses" of the elements they point to. I'm wondering to what extent this is true.
For example, do two elements *p1 and *p2 have the property p2 = p1 + 1 or p1 = p2 + 1 if and only if they are adjacent in physical memory?
You should think of pointers as being addresses of virtual memory: modern consumer operating systems and runtime environments place at least one layer of abstraction between physical memory and what you see as a pointer value.
As for your final statement, you cannot make that assumption, even in a virtual memory address space. Pointer arithmetic is only valid within blocks of contiguous memory such as arrays. And whilst it is permissible (in both C and C++) to assign a pointer to one point past an array (or scalar), the behaviour on deferencing such a pointer is undefined. Hypothesising about adjacency in physical memory in the context of C and C++ is pointless.
Not at all.
C++ is an abstraction over the code that your computer will perform. We see this abstraction leak in a few places (class member references requiring storage, for example) but in general you will be better off if you code to the abstraction and nothing else.
Pointers are pointers. They point to things. Will they be implemented as memory addresses in reality? Maybe. They could also be optimised out, or (in the case of e.g. pointers-to-members) they could be somewhat more complex than a simple numeric address.
When you start thinking of pointers as integers that map to addresses in memory, you begin to forget for example that it's undefined to hold a pointer to an object that doesn't exist (you can't just increment and decrement a pointer willy nilly to any memory address you like).
As many answers have already mentioned, they should not be thought of as memory addresses. Check out those answers and here to get an understanding of them. Addressing your last statement
*p1 and *p2 have the property p2 = p1 + 1 or p1 = p2 + 1 if and only if they are adjacent in physical memory
is only correct if p1 and p2 are of the same type, or pointing to types of the same size.
Absolutely right to think of pointers as memory addresses. That's what they are in ALL compilers that I have worked with - for a number of different processor architectures, manufactured by a number of different compiler producers.
However, the compiler does some interesting magic, to help you along with the fact that normal memory addresses [in all modern mainstream processors at least] are byte-addresses, and the object your pointer refers to may not be exactly one byte. So if we have T* ptr;, ptr++ will do ((char*)ptr) + sizeof(T); or ptr + n is ((char*)ptr) + n*sizeof(T). This also means that your p1 == p2 + 1 requires p1 and p2 to be of the same type T, since the +1 is actually +sizeof(T)*1.
There is ONE exception to the above "pointers are memory addresses", and that is member function pointers. They are "special", and for now, please just ignore how they are actually implemented, sufficient to say that they are not "just memory addresses".
The operating system provides an abstraction of the physical machine to your program (i.e. your program runs in a virtual machine). Thus, your program does not have access to any physical resource of your computer, be it CPU time, memory, etc; it merely has to ask the OS for these resources.
In the case of memory, your program works in a virtual address space, defined by the operating system. This address space has multiple regions, such as stack, heap, code, etc. The value of your pointers represent addresses in this virtual address space. Indeed, 2 pointers to consecutive addresses will point to consecutive locations in this address space.
However, this address space is splitted by the operating system into pages and segments, which are swapped in and out from memory as required, so your pointers may or may not point to consecutive physical memory locations and is impossible to tell at runtime if that is true or not. This also depends on the policy used by the operating system for paging and segmentation.
Bottom line is that pointers are memory addresses. However, they are addresses in a virtual memory space and it is up to the operating system to decide how this is mapped to the physical memory space.
As far as your program is concerned, this is not an issue. One reason for this abstraction is to make programs believe they are the only users of the machine. Imagine the nightmare you'd have to go through if you would need to consider the memory allocated by other processes when you write your program - you don't even know which processes are going to run concurrently with yours. Also, this is a good technique to enforce security: your process cannot (well, at least shouldn't be able to) access maliciously the memory space of another process since they run in 2 different (virtual) memory spaces.
Like other variables, pointer stores a data which can be an address of memory where other data is stored.
So, pointer is a variable that have an address and may hold an address.
Note that, it is not necessary that a pointer always holds an address. It may hold a non-address ID/handle etc. Therefore, saying pointer as an address is not a wise thing.
Regarding your second question:
Pointer arithmetic is valid for contiguous chunk of memory. If p2 = p1 + 1 and both pointers are of same type then p1 and p2 points to a contiguous chunk of memory. So, the addresses p1 and p2 holds are adjacent to each other.
I think this answer has the right idea but poor terminology. What C pointers provide are the exact opposite of abstraction.
An abstraction provides a mental model that's relatively easy to understand and reason about, even if the hardware is more complex and difficult to understand or harder to reason about.
C pointers are the opposite of that. They take possible difficulties of the hardware into account even when though the real hardware is often simpler and easier to reason about. They limit your reasoning to what's allowed by a union of the most complex parts of the most complex hardware regardless of how simple the hardware at hand may actually be.
C++ pointers add one thing that C doesn't include. It allows comparing all pointers of the same type for order, even if they're not in the same array. This allows a little more of a mental model, even if it doesn't match the hardware perfectly.
Somehow answers here fail to mention one specific family of pointers - that is, pointers-to-members. Those are certainly not memory addresses.
Unless pointers are optimized out by the compiler, they are integers that store memory addresses. Their lenght depends on the machine the code is being compiled for, but they can usually be treated as ints.
In fact, you can check that out by printing the actual number stored on them with printf().
Beware, however, that type * pointer increment/decrement operations are done by the sizeof(type). See for yourself with this code (tested online on Repl.it):
#include <stdio.h>
int main() {
volatile int i1 = 1337;
volatile int i2 = 31337;
volatile double d1 = 1.337;
volatile double d2 = 31.337;
volatile int* pi = &i1;
volatile double* pd = &d1;
printf("ints: %d, %d\ndoubles: %f, %f\n", i1, i2, d1, d2);
printf("0x%X = %d\n", pi, *pi);
printf("0x%X = %d\n", pi-1, *(pi-1));
printf("Difference: %d\n",(long)(pi)-(long)(pi-1));
printf("0x%X = %f\n", pd, *pd);
printf("0x%X = %f\n", pd-1, *(pd-1));
printf("Difference: %d\n",(long)(pd)-(long)(pd-1));
}
All variables and pointers were declared volatile so as the compiler wouldn't optimize them out. Also notice that I used decrement, because the variables are placed in the function stack.
The output was:
ints: 1337, 31337
doubles: 1.337000, 31.337000
0xFAFF465C = 1337
0xFAFF4658 = 31337
Difference: 4
0xFAFF4650 = 1.337000
0xFAFF4648 = 31.337000
Difference: 8
Note that this code may not work on all compilers, specially if they do not store variables in the same order. However, what's important is that the pointer values can actually be read and printed and that decrements of one may/will decrement based on the size of the variable the pointer references.
Also note that the & and * are actual operators for reference ("get the memory address of this variable") and dereference ("get the contents of this memory address").
This may also be used for cool tricks like getting the IEEE 754 binary values for floats, by casting the float* as an int*:
#include <iostream>
int main() {
float f = -9.5;
int* p = (int*)&f;
std::cout << "Binary contents:\n";
int i = sizeof(f)*8;
while(i) {
i--;
std::cout << ((*p & (1 << i))?1:0);
}
}
Result is:
Binary contents:
11000001000110000000000000000000
Example taken from https://pt.wikipedia.org/wiki/IEEE_754. Check out on any converter.
Pointers are memory addresses, but you shouldn't assume they reflect physical address. When you see addresses like 0x00ffb500 those are logical addresses that the MMU will translate to the corresponding physical address. This is the most probable scenario, since virtual memory is the most extended memory management system, but there could be systems that manage physical address directly
The particular example you give:
For example, do two elements *p1 and *p2 have the property p2 = p1 + 1 or p1 = p2 + 1 if and only if they are adjacent in physical memory?
would fail on platforms that do not have a flat address space, such as the PIC. To access physical memory on the PIC, you need both an address and a bank number, but the latter may be derived from extrinsic information such as the particular source file. So, doing arithmetic on pointers from different banks would give unexpected results.
According to the C++14 Standard, [expr.unary.op]/3:
The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C::m. Otherwise, if the type of the expression is T, the result has type “pointer to T” and is a prvalue that is the address of the designated object or a pointer to the designated function. [Note: In particular, the address of an object of type “cv T” is “pointer to cv T”, with the same cv-qualification. —end note ]
So this says clearly and unambiguously that pointers to object type (i.e. a T *, where T is not a function type) hold addresses.
"address" is defined by [intro.memory]/1:
The memory available to a C++ program consists of one or more sequences of contiguous bytes. Every byte has a unique address.
So an address may be anything which serves to uniquely identify a particular byte of memory.
Note: In the C++ standard terminology, memory only refers to space that is in use. It doesn't mean physical memory, or virtual memory, or anything like that. The memory is a disjoint set of allocations.
It is important to bear in mind that, although one possible way of uniquely identifying each byte in memory is to assign a unique integer to each byte of physical or virtual memory, that is not the only possible way.
To avoid writing non-portable code it is good to avoid assuming that an address is identical to an integer. The rules of arithmetic for pointers are different to the rules of arithmetic for integers anyway. Similarly, we would not say that 5.0f is the same as 1084227584 even though they have identical bit representations in memory (under IEEE754).
My background is C++ and I'm currently about to start developing in C# so am doing some research. However, in the process I came across something that raised a question about C++.
This C# for C++ developers guide says that
In C++ an array is merely a pointer.
But this StackOverflow question has a highly-upvoted comment that says
Arrays are not pointers. Stop telling people that.
The cplusplus.com page on pointers says that arrays and pointers are related (and mentions implicit conversion, so they're obviously not the same).
The concept of arrays is related to that of pointers. In fact, arrays work very much like pointers to their first elements, and, actually, an array can always be implicitly converted to the pointer of the proper type.
I'm getting the impression that the Microsoft page wanted to simplify things in order to summarise the differences between C++ and C#, and in the process wrote something that was simpler but not 100% accurate.
But what have arrays got to do with pointers in the first place? Why is the relationship close enough for them to be summarised as the "same" even if they're not?
The cplusplus.com page says that arrays "work like" pointers to their first element. What does that mean, if they're not actually pointers to their first element?
There is a lot of bad writing out there. For example the statement:
In C++ an array is merely a pointer.
is simply false. How can such bad writing come about? We can only speculate, but one possible theory is that the author learned C++ by trial and error using a compiler, and formed a faulty mental model of C++ based on the results of his experiments. This is possibly because the syntax used by C++ for arrays is unconventional.
The next question is, how can a learner know if he/she is reading good material or bad material? Other than by reading my posts of course ;-) , participating in communities like Stack Overflow helps to bring you into contact with a lot of different presentations and descriptions, and then after a while you have enough information and experience to make your own decisions about which writing is good and which is bad.
Moving back to the array/pointer topic: my advice would be to first build up a correct mental model of how object storage works when we are working in C++. It's probably too much to write about just for this post, but here is how I would build up to it from scratch:
C and C++ are designed in terms of an abstract memory model, however in most cases this translates directly to the memory model provided by your system's OS or an even lower layer
The memory is divided up into basic units called bytes (usually 8 bits)
Memory can be allocated as storage for an object; e.g. when you write int x; it is decided that a particular block of adjacent bytes is set aside to store an integer value. An object is any region of allocated storage. (Yes this is a slightly circular definition!)
Each byte of allocated storage has an address which is a token (usually representible as a simple number) that can be used to find that byte in memory. The addresses of any bytes within an object must be sequential.
The name x only exists during the compilation stage of a program. At runtime there can be int objects allocated that never had a name; and there can be other int objects with one or more names during compilation.
All of this applies to objects of any other type, not just int
An array is an object which consists of many adjacent sub-objects of the same type
A pointer is an object which serves as a token identifying where another object can be found.
From hereon in, C++ syntax comes into it. C++'s type system uses strong typing which means that each object has a type. The type system extends to pointers. In almost all situations, the storage used to store a pointer only saves the address of the first byte of the object being pointed to; and the type system is used at compilation time to keep track of what is being pointed to. This is why we have different types of pointer (e.g. int *, float *) despite the fact that the storage may consist of the same sort of address in both cases.
Finally: the so-called "array-pointer equivalence" is not an equivalence of storage, if you understood my last two bullet points. It's an equivalence of syntax for looking up members of an array.
Since we know that a pointer can be used to find another object; and an array is a series of many adjacent objects; then we can work with the array by working with a pointer to that array's first element. The equivalence is that the same processing can be used for both of the following:
Find Nth element of an array
Find Nth object in memory after the one we're looking at
and furthermore, those concepts can be both expressed using the same syntax.
They are most definitely not the same thing at all, but in this case, confusion can be forgiven because the language semantics are ... flexible and intended for the maximum confusion.
Let's start by simply defining a pointer and an array.
A pointer (to a type T) points to a memory space which holds at least one T (assuming non-null).
An array is a memory space that holds multiple Ts.
A pointer points to memory, and an array is memory, so you can point inside or to an array. Since you can do this, pointers offer many array-like operations. Essentially, you can index any pointer on the presumption that it actually points to memory for more than one T.
Therefore, there's some semantic overlap between (pointer to) "Memory space for some Ts" and "Points to a memory space for some Ts". This is true in any language- including C#. The main difference is that they don't allow you to simply assume that your T reference actually refers to a space where more than one T lives, whereas C++ will allow you to do that.
Since all pointers to a T can be pointers to an array of T of arbitrary size, you can treat pointers to an array and pointers to a T interchangably. The special case of a pointer to the first element is that the "some Ts" for the pointer and "some Ts" for the array are equal. That is, a pointer to the first element yields a pointer to N Ts (for an array of size N) and a pointer to the array yields ... a pointer to N Ts, where N is equal.
Normally, this is just interesting memory crapping-around that nobody sane would try to do. But the language actively encourages it by converting the array to the pointer to the first element at every opportunity, and in some cases where you ask for an array, it actually gives you a pointer instead. This is most confusing when you want to actually use the array like a value, for example, to assign to it or pass it around by value, when the language insists that you treat it as a pointer value.
Ultimately, all you really need to know about C++ (and C) native arrays is, don't use them, pointers to arrays have some symmetries with pointers to values at the most fundamental "memory as an array of bytes" kind of level, and the language exposes this in the most confusing, unintuitive and inconsistent way imaginable. So unless you're hot on learning implementation details nobody should have to know, then use std::array, which behaves in a totally consistent, very sane way and just like every other type in C++. C# gets this right by simply not exposing this symmetry to you (because nobody needs to use it, give or take).
Arrays and pointers in C and C++ can be used with the exact same semantics and syntax in the vast majority of cases.
That is achieved by one feature:
Arrays decay to pointers to their first element in nearly all contexts.
Exceptions in C: sizeof, _Alignas, _Alignas, address-of &
In C++, the difference can also be important for overload-resolution.
In addition, array notation for function arguments is deceptive, these function-declarations are equivalent:
int f(int* a);
int f(int a[]);
int f(int a[3]);
But not to this one:
int f(int (&a)[3]);
Besides what has already been told, there is one big difference:
pointers are variables to store memory addresses, and they can be incremented or decremented and the values they store can change (they can point to any other memory location). That's not the same for arrays; once they are allocated, you can't change the memory region they reference, e.g. you cannot assign other values to them:
int my_array[10];
int x = 2;
my_array = &x;
my_array++;
Whereas you can do the same with a pointer:
int *p = array;
p++;
p = &x;
The meaning in this guide was simply that in C# an array is an object (perhaps like in STL that we can use in C++), while in C++ an array is basically a sequence of variables located & allocated one after the other, and that's why we can refer to them using a pointer (pointer++ will give us the next one etc.).
it's as simple as:
int arr[10];
int* arr_pointer1 = arr;
int* arr_pointer2 = &arr[0];
so, since arrays are contiguous in memory, writing
arr[1];
is the same as writing:
*(arr_pointer+1)
pushing things a bit further, writing:
arr[2];
//resolves to
*(arr+2);
//note also that this is perfectly valid
2[arr];
//resolves to
*(2+arr);
In "Computer System: A Programmer's Perspective", section 2.1 (page 31), it says:
The value of a pointer in C is the virtual address of the first byte of some block of storage.
To me it sounds like the C pointer's value can take values from 0 to [size of virtual memory - 1]. Is that the case? If yes, I wonder if there is any mechanism that checks if all pointers in a program are assigned with legal values -- values at least 0 and at most [size of virtual memory - 1], and where such mechanism is built in -- in compiler? OS? or somewhere else?
There is no process that checks pointers for validity as use of invalid pointers has undefined effects anyway.
Usually it will be impossible for a pointer to hold a value outside of the addressable range as the two will have the same available range — e.g. both will be 32 bit. However some CPUs have rules about pointer alignment that may render some addresses invalid for some types of data. Some runtimes, such as 64-bit Objective-C, which is a strict superset of C, use incorrectly aligned pointers to disguise literal objects as objects on the heap.
There are also some cases where the complete address space is defined by the instruction set to be one thing but is implemented by that specific hardware to be another. An example from history is the original 68000 which defined a 32-bit space but had only 24 address lines. Very early versions of Mac OS used the spare 8 bits for flags describing the block of data, relying on the hardware to ignore them.
So:
there's no runtime checking of validity;
even if there were, the meaning of validity is often dependent on the specific model of CPU (not just the family) or specific version of the OS (ditto) so as to make checking a less trivial task than you might guess.
In practise what will normally happen if your address is illegal per that hardware but is accessed as though legal is a processor exception.
A pointer in C is an abstract object. The only guarantee provided by the C standard is that pointers can point to all the things they need to within C: functions, objects, one past the end of an object, and NULL.
In typical C implementations, pointers can point to any address in virtual memory, and some C implementations deliberately support this in large part. However, there are complications. For example, the value used for NULL may be difficult to use as an address, and converting pointers created for one type to another type may fail (due to alignment problems). Additionally, there are legal non-typical C implementations where pointers do not directly correlate to memory addresses in a normal way.
You should not expect to use pointers to access memory arbitrarily without understanding the rules of the C standard and of the C implementations you use.
There is no mechanism in C which will check if pointers in a program are valid. The programmer is responsible for using them correctly.
For practical purposes a C pointer is either NULL or a memory address to something else. I've never heard of NULL being anything but zero in real life. If it's a memory address you're not supposed to "care" what the actual number is; just pass it around, dereference it etc.
How do memory addresses work?
In 32-bit a memory address is an hexadecimal value like 0x0F032010, right? But do those values point to bytes or to bits?
And what lies between two memory addresses like 0x0F032010 and 0x0F032011
In 32-bit a memory address is an hexadecimal value like 0x0F032010, right?
Its a number. A location in memory. It is bounded by the start and end of memory, which is starting at some value and ending at some value.
But do those values point to bytes or to bits?
It is generally accepted that addresses point to the smallest addressable unit, which is a byte. Most modern CPUs are defined this way. This is however not always the case.
And what lies between two memory addresses like 0x0F032010 and 0x0F032011
Dragons. Or nothing, as there isn't anything between them.
In C and in C++, addresses ultimately point to something that is the same size as a char -- a "byte". That is the level of addressing in the language. Whether that truly is the level of addressing in the machine at hand is a different question. The number of bits in a byte is yet another question. The standard specifies a minimal value.
A C++ (or C) address, or pointer value, is best thought of as pointing to an object, not (necessarily) to a byte. Pointer arithmetic is defined in terms of the size of the pointed-to object, so incrementing an int* value gives you a pointer to the adjacent int object (which might be, say, 4 bytes farther along in memory).
On the machine level, assuming a typical linear monolithic byte-addressed memory model, pointers are implemented as machine addresses, and each address is effectively a number that refers to a single byte. An int* pointer value contains the address of the first byte of the int object it points to. Incrementing a C++ int* pointer is implemented by adding sizeof (int) (say, 4) to the machine address.
As far as C++ is concerned, pointers are not integers; they're just pointers. You can use casts to convert between pointer values and integer values, and the result should be meaningful in terms of the underlying machine's memory model, but not much is guaranteed about the results. You can perform arithmetic on pointers, but on the language level that's not at all the same thing as integer arithmetic (though it's probably implemented as scaled integer arithmetic on the machine level).
The memory model I've described is not the only possible one, and both C and C++ are deliberately designed to allow other models. For example, you could have a model where each individual object has its own memory space, and a pointer or address is a composite value consisting of something that identifies the object plus an offset within that object. Given int x; int y;, you can compare their addresses for equality (&x == &y will be false), but the behavior of &x < &y is undefined; it's not even required that &x < &y and &y < &x have opposite values.
The C++ memory model works very nicely on top of a typical 32-bit flat memory model, and you can think of pointers as numbers. But the C++ model is sufficiently abstract that it can also work on top of other models.
You can think about pointers and addresses either in the abstract terms defined by the language, or in the concrete terms implemented by the machine. They're quite different, but ultimately compatible, mental models. Keeping them both in your head simultaneously can be tricky.
Those values are nothing in themselves, just numbers. A memory address is just a number that corresponds to a byte of memory, like the street address for your house. It's only a number.
The smallest unit an address can point to is a byte, so addresses point to bytes; you can think of the address pointing to the left side of your house; the actual house (the bits that make up the byte) is between your address (which points to the left side of your house) and your next door neighbor's address (pointing to the left side of his house). But there aren't any other addresses between there.