I've been studying C++ for couple of months now and just recently decided to look more deeply into the logic of pointers and arrays. What I've been taught in uni is pretty basic - pointers contain the address of a variable. When an array is created, basically a pointer to its first element is created.
So I started experimenting a bit. (and got to a conclusion which I need confirmation for). First of all I created
int arr[10];
int* ptr = &arr[5];
And as you would imagine
cout << ptr[3];
gave me the 8th element of the array. Next I tried
int num = 6;
int* ptr2 = #
cout << ptr2[5];
cout << ptr2 + 5;
which to my great delight (not irony) returned the same addresses. Even though num wasn't an array.
The conclusion to which I got: array is not something special in C++. It's just a pointer to the first element (already typed that). More important: Can I think about every pointer in the manner of object of a class variable*. Is the operator [] just overloaded in the class int*? For example to be something along the lines of:
int operator[] (int index){
return *(arrayFirstaddress + index);
}
What was interesting to me in these experiments is that operator [] works for EVERY pointer. (So it's exactly like overloading an operator for all instances of the said class)
Of course, I can be as wrong as possible. I couldn't find much information in the web, since I didn't know how to word my question so I decided to ask here.
It would be extremely helpful if you explained to me if I'm right/wrong/very wrong and why.
You find the definition of subscripting, i.e. an expression like ptr2[5] in the c++ standard, e.g. like in this online c++ draft standard:
5.2.1 Subscripting [expr.sub]
(1) ... The expression E1[E2] is identical (by definition) to
*((E1)+(E2))
So your "discovery" sounds correct, although your examples seem to have some bugs (e.g. ptr2[5] should not return an address but an int value, whereas ptr2+5 is an address an not an int value; I suppose you meant &ptr2[5]).
Further, your code is not a prove of this discovery as it is based on undefined behaviour. It may yield something that supports your "discovery", but your discovery could still be not valid, and it could also do the opposite (really!).
The reason why it is undefined behaviour is that even pointer arithmetics like ptr2+5 is undefined behaviour if the result is out of the range of the allocated memory block ptr2 points to (which is definitely the case in your example):
5.7 Additive operators
(6) ... Unless both pointers point to elements of the same array
object, or one past the last element of the array object, the behavior
is undefined.
Different compilers, different optimization settings, and even slight modifications anywhere in your program may let the compiler do other things here.
An array in C++ is a collection of objects. A pointer is a variable that can store the address of something. The two are not the same thing.
Unfortunately, your sample
int num = 6;
int* ptr2 = #
cout << ptr2[5];
cout << ptr2 + 5;
exhibits undefined behaviour, both in the evaluation of ptr2[5] and ptr2 + 5. Pointer expressions are special - arithmetic involving pointers only has defined behaviour if the pointer being acted on (ptr2 in this case) and the result (ptr2 + 5) are within the same object. Or one past the end (although dereferencing a "one past the end" pointer - trying to access the value it points at - also gives undefined behaviour).
Semantically, *(ptr + n) and ptr[n] are equivalent (i.e. they have the same meaning) if ptr is a pointer and n is an integral value. So if evaluating ptr + n gives undefined behaviour, so does evaluating ptr[n]. Similarly, &ptr[n] and ptr + n are equivalent.
In expressions, depending on context, the name of an array is converted to a pointer, and that pointer is equal to the address of that array's first element. So, given
int x[5];
int *p;
// the following all have the same effect
p = x + 2;
p = &x[0] + 2;
p = &x[2];
That does not mean an array is a pointer though.
Related
Consider the following code:
int a[25][80];
a[0][1234] = 56;
int* p = &a[0][0];
p[1234] = 56;
Does the second line invoke undefined behavior? How about the fourth line?
Both lines do result in undefined behavior.
Subscripting is interpreted as pointer addition followed by an indirection, that is, a[0][1234]/p[1234] is equivalent to *(a[0] + 1234)/*(p + 1234). According to [expr.add]/4 (here I quote the newest draft, while for the time OP is proposed, you can refer to this comment, and the conclusion is the same):
If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i+j] if 0≤i+j≤n; otherwise, the behavior is undefined.
since a[0](decayed to a pointer to a[0][0])/p points to an element of a[0] (as an array), and a[0] only has size 80, the behavior is undefined.
As Language Lawyer pointed out in the comment, the following program does not compile.
constexpr int f(const int (&a)[2][3])
{
auto p = &a[0][0];
return p[3];
}
int main()
{
constexpr int a[2][3] = { 1, 2, 3, 4, 5, 6, };
constexpr int i = f(a);
}
The compiler detected such undefined behaviors when it appears in a constant expression.
It's up to interpretation. While the contiguity requirements of arrays don't leave much to the imagination in terms of how to layout a multidimensional arrays (this has been pointed out before), notice that when you're doing p[1234] you're indexing the 1234th element of the zeroth row of only 80 columns. Some interpret the only valid indices to be 0..79 (&p[80] being a special case).
Information from the C FAQ which is the collected wisdom of Usenet on matters relevant to C. (I do not think C and C++ differ on that matter and that this is very much relevant.)
In the language the Standard was written to describe, there would be no problem with invoking a function like:
void print_array(double *d, int rows, int cols)
{
int r,c;
for (r = 0; r < rows; r++)
{
printf("%4d: ", r);
for (c = 0; c < cols; c++)
printf("%10.4f ", d[r*cols+c]);
printf("\n");
}
}
on a double[10][4], or a double[50][40], or any other size, provided that the total number of elements in the array was less than rows*cols. Indeed, the guarantee that the row stride of a T[R][C] would equal C * sizeof (T) was designed among other things to make it possible to write code that could work with arbitrarily-sized multi-dimensional arrays.
On the other hand, the authors of the Standard recognized that when implementations are given something like:
double d[10][10];
double test(int i)
{
d[1][0] = 1.0;
d[0][i] = 2.0;
return d[1][0];
}
allowing them to generate code that would assume that d[1][0] would still hold 1.0 when the return executes, or allowing them to generate code that would trap if i is greater than 10, would allow them to be more suitable for some purposes than requiring that they silently return 2.0 if invoked with i==10.
Nothing in the Standard makes any distinction between those scenarios. While it would have been possible for the Standard to have included rules that would say that the second example invokes UB if i >= 10 without affecting the first example (e.g. say that applying [N] to an array doesn't cause it to decay to a pointer, but instead yields the Nth element, which must exist in that array), the Standard instead relies upon the fact that implementations are allowed to behave in useful fashion even when not required to do so, and compiler writers should presumably be capable of recognizing situations like the first example when doing so would benefit their customers.
Since the Standard never sought to fully define everything that programmers would need to do with arrays, it should not be looked to for guidance as to what constructs quality implementations should support.
Your compiler will throw a bunch of warnings/errors because of subscript out of range (line 2) and incompatioble types (line 3), but as long as the actual variable (int in this case) is one of the intrinsic base-types this is save to do in C and C++.
(If the variable is a class/struct it will probably still work in C, but in C++ all bets are off.)
Why you would want to do this....
For the 1st variant: If your code relies on this sort of messing about it will be error-prone and hard to maintain in the long run.
I can see a some use for the second variant when performance optimizing loops over 2D arrays by replacing them by a 1D pointer run over the data-space, but a good optimizing compiler will often do that by itself anyway.
If the body of the loop is so big/complex the compiler can't optimize/replace the loop by a 1D run on it's own, the performance gain by doing it manually will most likely not be significant either.
You're free to reinterpret the memory any way you'd like. As long as the multiple does not exceed the linear memory. You can even move a to 12, 40 and use negative indexes.
The memory referenced by a is both a int[25][80] and a int[2000]. So says the Standard, 3.8p2:
[ Note: The lifetime of an array object starts as soon as storage with proper size and alignment is obtained, and its lifetime ends when the storage which the array occupies is reused or released. 12.6.2 describes the lifetime of base and member subobjects. — end note ]
a has a particular type, it is an lvalue of type int[25][80]. But p is just int*. It is not "int* pointing into a int[80]" or anything like that. So in fact, the int pointed to is an element of int[25][80] named a, and also an element of int[2000] occupying the same space.
Since p and p+1234 are both elements of the same int[2000] object, the pointer arithmetic is well-defined. And since p[1234] means *(p+1234), it too is well-defined.
The effect of this rule for array lifetime is that you can freely use pointer arithmetic to move through a complete object.
Since std::array got mentioned in the comments:
If one has std::array<std::array<int, 80>, 25> a; then there does not exist a std::array<int, 2000>. There does exist a int[2000]. I'm looking for anything that requires sizeof (std::array<T,N>) == sizeof (T[N]) (and == N * sizeof (T)). Absent that, you have to assume that there could be gaps which mess up traversal of nested std::array.
This question already has answers here:
Why am I being told that an array is a pointer? What is the relationship between arrays and pointers in C++?
(6 answers)
Closed 5 years ago.
I am trying to understand arrays and pointers in c++.
In a past project, I did. I created a pointer and then assigned that pointer to an array of the same type. I've read previous posts, though, that says you can't assign a pointer to an array. Also from the pointer, I was able to use the index to get a certain element (square brackets []).
So I was wondering if anyone could explain to me arrays and pointers in C++ and why something like this is possible, I've tried looking for it online, but haven't found anything.
Example,
#include <iostream>
using namespace std;
int main()
{
int *pointer = new int[10];
for (int i = 0; i < 10; i++)
{
pointer[i] = i;
}
for (int i = 0; i < 10; i++)
{
cout << pointer[i];
}
return 1;
}
The main difference between arrays and pointers is that they are completely different things.
As array is a collection of objects, which is laid out contiguously in memory. For example, int x[5] defines an array named x, which is a collection of 5 integers, laid out side by side in memory. Individual elements in the array may be accessed using "array syntax" of the form x[i] where i is an integral value with values between 0 and 4. (Other values of i will result in undefined behaviour).
A pointer is a variable which holds a value that is an address in memory. For example, int *p defines p as a pointer to an int, and it can be initialised with the address of a variable of type int. For example, p = &some_int causes p to contain the address of some_int. When that is done, the notation *p (called dereferencing) provides access to the pointed-to variable. For example, *p = 42 will set some_int to have the value 42.
You'll notice, in the description above, I have not used the word "pointer" in describing an array, nor have I used the word "array" to describe a pointer. They are completely different things.
However, they can be used in ways that makes them seem the same, because of a few rules in the language. Firstly, there is a conversion called the "array-to-pointer" conversion. Because of this, it is possible to do
int x[5];
int *p = x;
The initialisation of p actually works by using the array-to-pointer conversion. Because it is being used to initialise a pointer, the compiler implicitly converts x to a pointer, equal to the address of x[0]. To do this explicitly (without the compiler silently and sneakily doing a conversion) you could have written
int *p = &x[0];
and got exactly the same effect. Either way, the assignment *p = 42 will subsequently have the effect of assigning x[0] to 42.
That suggests there is a relationship between expressions involving pointers and expressions involving (the name of) arrays. If p is equal to &x[0], then
p + i is equivalent to &x[i]; AND
*(p + i) is equivalent to x[i].
The language rules of C and C++ make these relationships symmetric, so (look carefully here)
x + i is equivalent to &x[i]; AND
*(x + i) is equivalent to x[i]
and, with pointers
p + i is equivalent to &p[i]; AND
*(p + i) is equivalent to p[i]
Which basically means that pointer syntax can be used to work with arrays (thanks to the pointer-to-array conversion) AND array syntax can be used to work with pointers.
Really bad textbooks then go on from this and conclude that pointers are arrays and that arrays are pointers. But they are not. If you find textbooks which say such things, burn them. Arrays and pointers are different things entirely. What we have here is a syntactic equivalence - even though arrays and pointers are different things entirely, they can be worked on using the same syntax.
One of the differences - where the syntactic equivalence does not apply - is that arrays cannot be reassigned. For example;
int x[5];
int y[5];
int *p = y; // OK - pointer to array conversion
x = y; // error since x is an array
x = p; // error since x is an array
The last two statements will be diagnosed by a C or C++ compiler as an error, because x is an array.
Your example
int *pointer = new int[10];
is a little different again. pointer is still not an array. It is a pointer, initialised with a "new expression", which dynamically allocates an array of 10 integers. But because of the syntactic equivalence of pointers and arrays, pointer can be treated syntactically AS IF it is an array of 10 elements.
Note: the above is concerned with raw arrays. The C++ standard library also has a type named std::array which is a data structure which contains an array, but behaves somewhat differently than described here.
a pointer in reality is a variable which keeps an address of a memory it points to. The memory itself can be allocated by one of the heap management functions like 'malloc' or 'new' or some others. So, in general you ask the function to allocate a certain amount of memory and return its address. You keep the latter as the pointer to it in a variable.
Arrays in 'c++/c' are contiguous chunks of memory. So, there is no difference between allocating 40 bytes or 10 integers in the array (assuming that an int is 4 byte long).
'c/c++' also understand the type of the data which the allocated memory contains. The languages provide a feature named 'pointer arithmetic'. This actually means that you can add to a pointer or subtract from it. The language will factor the values by the size of the type. I.e.
int *a = new ...
int b = *(a+4);
In the above case value of 'b' will be the same as the value saved in memory 'a' with offset of 16 bytes.
The above is very similar to the array indexing arithmetic and the language allow you to use the array indexing instead of the above:
b = a[4];
So, in this sense both worlds intersect and you can interchangeably use either pointer arithmetic or array arithmetic in the language.
Arrays do not have to be allocated on the heap as well as the pointers do hot have to address the heap only. You can have an array allocated on stack or in the global scope and have a pointer to it:
int myarray[10];
int *pointer = myarray;
How you can apply either the pointer arithmetic or array arithmetic to the pointer. the following are equivalent (if you did not advance the pointer)
myarray[3]
pointer[3]
*(pointer + 3)
Hope it clarifies the issue for you.
From wikipedia:
A pointer references a location in memory, and obtaining the value
stored at that location is known as dereferencing the pointer
Arrays are the contiguous memory locations and their location in memory is referenced by a pointer.
int *pointer = new int[10];
for (int i = 0; i < 10; i++)
{
pointer[i] = i;
}
the code above is actually accesses pointer + i * sizeof(int) thanks to operator[] (assume variable pointer is an integer or smth like that, no pointer arithmetic here.)
this is my first question in this forum, sorry my bad english.
I have a question about pointers and dynamic memory in c++.
Example, this code:
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int *a = new int;
for (int i = 0; i < 5; i++)
cout << a++ << endl;
return 0;
}
Output:
0x11d4c20
0x11d4c24
0x11d4c28
0x11d4c2c
0x11d4c30
My question, is why can I move more than that 'single' block of memory that I created with new.
What is a pointing to?
Same occurs with new int[], even if I specific the size:
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int *a = new int[2];
for (int i = 0; i < 5; i++)
cout << a++ << endl;
return 0;
}
Output:
0x2518c20
0x2518c24
0x2518c28
0x2518c2c
0x2518c30
Again, what is happening?
What is a pointing to?
Does all of this mean I'm violating memory?
a is an int*, not an int. What you are printing is actually the pointer, i.e. the memory address of the pointed object. Use the dereference operator * whenever you want to modify the pointed value, i.e.
cout << (*a)++ << endl;
NB: Likewise, you can get a pointer to an int using the reference operator, &, not to be mixed up with a reference (e.g. a int& type).
This may print 0 1 2 3 4. may because you are not initializing the new int created in dynamic memory. This means reading from *a (dereferenced a) is undefined behavior, which means your program may misbehave. You have to change your line using new:
int *a = new int();
This will initialize *a to 0 and now 0 1 2 3 4 will be printed correctly.
Note that int *a = new int[2]; does create a dynamic array of 2 entries in dynamic memory, which means *(a + 1) can be used as well (as if it was a regular array). It does not initialize *a to 2.
Do remember to delete a; when you've done using it. In a real application, you could get a memory leak if you don't - i.e. your program would still use memory it doesn't need anymore. Caution, when you have to delete a dynamically-allocated array (i.e. new int[2]), you need to use delete[] a; instead, or you will trigger undefined behavior.
You may also use a unique_ptr (or a shared_ptr) in C++11 as an alternative to this kind of memory allocation, i.e. :
#include <memory>
// ...
std::unique_ptr<int> a = std::make_unique<int>(0);
Thanks to this solution, you do not need to delete a because the unique_ptr will do this for you, when itself dies (i.e. out of the scope, here).
Edit: Bonus:
0x2518c20
0x2518c24
0x2518c28
Why is the number incremented by 4 if you just used ++?
Using ++ on an address will actually increment it by sizeof(T), which here is sizeof(int) and not 1. This explains why you can, as previously stated, use *(a + 1) if you used new int[2].
i think that it is ,because a points to an integer , the size of an integer is 4 bytes (sizeof(int) == 4) after executing a++ ,a points to the next integer,try char *a, and a++ to be more sure
It is legal to point at an object or the spot right after an object. A new int[2] creates two adjacent objects (in an array) and returns a pointer to the first one.
So yes, what you did above is not permitted. The behaviour of adding 5 to a pointer to a single object is not defined by the C++ standard; the compiler can generate assembly that does anything at all.
As it happens, on a flat memory architecture, pointers are basically unsigned integers. And incrementing a pointer by 1 is just incrementing it by the size of the pointed-to object.
So what often happens is you just get pointers to whatever happens to be there.
Now this is not guaranteed and should not be relied upon. Many of the rules of C++ that make actions undefined permit certain optimizations to occur over the "naive" mapping you might think the compiler does. For example, pointers to short can never point to an int and change its value in a defined way, which means if a function has both an int and short pointer it can assume that a write to the short does not modify the int.
The naive "write to the two words in an int" method of using shorts can work, then not work for seeming no reason, because the behaviour was undefined and the compiler was free to optimize assuming it could not happen.
In short, your actions are not legal, the behaviour you got is not surprising, but you can never rely on it.
Pointers are not just unsigned integers, even if that is what your compiler implements them with. They are an abstraction. Their behaviour is determined not by their implementation, but rather what the standard permits you do with them. When you act in ways the standard does not permit, the behaviour you get is undefined by the standard. Compilers can, and have been known to, exploit that fact to assume undefined behaviour cannot and does not occur. The program could behave unexpectedly on lines of code prior to the undefined behaviour as the compiler reorders and optimizes based on the assumption your code has well defined behaviour.
Pointers in c++ (and c) are just addresses to the memory (32/64bit numbers). There is no problem with increasing them or decreasing any way you want and you are not violating the memory or any other rule. You would be however violating the memory if you tried to read or write to the address pointed to by A after going through the for cycle.
As for what it is pointing to, most likely it's just some more space allocated to you by the new (and malloc under it) because it tends to give more space than you ask for, though this behaviour is not guaranteed. It might also point to heap data or just unassigned memory.
Once I encountered following code:
int s =10;
int *p=&s;
cout << p[3] << endl;
And I can't understand why am I able to access p[3] that doesn't exist (only p exists that is single pointer but I still get access to p[3] that is array that I have never created).
Is it some compiler bug or it is a feature or I don't know some basics of C++ that covers this?
Thank you
Why does C++ consider pointer and array of pointers as same thing?
It doesn't. You're asking why it treats pointers and arrays as the same.
The [] operator is just an abbreviated form of pointer arithmetic. a[b] is equivalent to *(a + b). Array names can decay into pointers, and then pointer arithmetic is applied. It's the programmers job to make sure they don't go out of bounds. The compiler can't possibly stop you from shooting your foot off.
Also, claiming to be able to "access" it is a strong assertion. That is UB, and is most likely going to either read the wrong memory or get a segfault.
No, it's not a compiler bug, its a very useful feature... but lets not get ahead of ourselves here, the consequence of your code is called Undefined Behaviour
So, what's the feature? All naked arrays are actually pointer to the first element. Except un-decayed arrays (See What is array decaying?).
Consider this code:
int s =10;
int* array = new int[12];
int *p;
p = array; // p refers to the first element
int* x = p + 7; //advances to the 7th element, compiler never checks bounds
int* y = p + 700; //ditto ...this is obviously undefined
p = &s; //p is now pointing to where s
int* xx = p + 3; //But s is a single element, so Undefined Behaviour
Once an array is decayed, it's simply a pointer... And a pointer can be incremented, decremented, dereferenced, advanced, assigned or reassigned.
So,
cout << p[7] << endl;
is a valid C++ program. but not necessarily correct.
It's the responsibility of the programmer to know whether a pointer points to a single element or an array. but thanks to static analyzers and https://github.com/isocpp/CppCoreGuidelines, things are changing for good.
Also see What are all the common undefined behaviours that a C++ programmer should know about?
From here, section array-to-pointer decay:
There is an implicit conversion from lvalues and rvalues of array type to rvalues of pointer type: it constructs a pointer to the first element of an array. This conversion is used whenever arrays appear in context where arrays are not expected, but pointers are
Inherited from C, C++ allows you to treat any pointer like the first element of an array starting at that address.
That's in part because it passes arrays by reference as pointers and so for that to make sense you need to be able to treat a pointer as an array.
It also enables some quite neat and very efficient code in various circumstances.
The upshot is that p[3] is a valid construct in this context.
Obviously however it has undefined behaviour because p isn't pointing to an array! Unfortunately the language rules (and compiler) aren't smart enough to work that out.
C is a very low level language and doesn't enforce nice things like range checking either during compilation or execution.
Consider the following code:
int a[25][80];
a[0][1234] = 56;
int* p = &a[0][0];
p[1234] = 56;
Does the second line invoke undefined behavior? How about the fourth line?
Both lines do result in undefined behavior.
Subscripting is interpreted as pointer addition followed by an indirection, that is, a[0][1234]/p[1234] is equivalent to *(a[0] + 1234)/*(p + 1234). According to [expr.add]/4 (here I quote the newest draft, while for the time OP is proposed, you can refer to this comment, and the conclusion is the same):
If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i+j] if 0≤i+j≤n; otherwise, the behavior is undefined.
since a[0](decayed to a pointer to a[0][0])/p points to an element of a[0] (as an array), and a[0] only has size 80, the behavior is undefined.
As Language Lawyer pointed out in the comment, the following program does not compile.
constexpr int f(const int (&a)[2][3])
{
auto p = &a[0][0];
return p[3];
}
int main()
{
constexpr int a[2][3] = { 1, 2, 3, 4, 5, 6, };
constexpr int i = f(a);
}
The compiler detected such undefined behaviors when it appears in a constant expression.
It's up to interpretation. While the contiguity requirements of arrays don't leave much to the imagination in terms of how to layout a multidimensional arrays (this has been pointed out before), notice that when you're doing p[1234] you're indexing the 1234th element of the zeroth row of only 80 columns. Some interpret the only valid indices to be 0..79 (&p[80] being a special case).
Information from the C FAQ which is the collected wisdom of Usenet on matters relevant to C. (I do not think C and C++ differ on that matter and that this is very much relevant.)
In the language the Standard was written to describe, there would be no problem with invoking a function like:
void print_array(double *d, int rows, int cols)
{
int r,c;
for (r = 0; r < rows; r++)
{
printf("%4d: ", r);
for (c = 0; c < cols; c++)
printf("%10.4f ", d[r*cols+c]);
printf("\n");
}
}
on a double[10][4], or a double[50][40], or any other size, provided that the total number of elements in the array was less than rows*cols. Indeed, the guarantee that the row stride of a T[R][C] would equal C * sizeof (T) was designed among other things to make it possible to write code that could work with arbitrarily-sized multi-dimensional arrays.
On the other hand, the authors of the Standard recognized that when implementations are given something like:
double d[10][10];
double test(int i)
{
d[1][0] = 1.0;
d[0][i] = 2.0;
return d[1][0];
}
allowing them to generate code that would assume that d[1][0] would still hold 1.0 when the return executes, or allowing them to generate code that would trap if i is greater than 10, would allow them to be more suitable for some purposes than requiring that they silently return 2.0 if invoked with i==10.
Nothing in the Standard makes any distinction between those scenarios. While it would have been possible for the Standard to have included rules that would say that the second example invokes UB if i >= 10 without affecting the first example (e.g. say that applying [N] to an array doesn't cause it to decay to a pointer, but instead yields the Nth element, which must exist in that array), the Standard instead relies upon the fact that implementations are allowed to behave in useful fashion even when not required to do so, and compiler writers should presumably be capable of recognizing situations like the first example when doing so would benefit their customers.
Since the Standard never sought to fully define everything that programmers would need to do with arrays, it should not be looked to for guidance as to what constructs quality implementations should support.
Your compiler will throw a bunch of warnings/errors because of subscript out of range (line 2) and incompatioble types (line 3), but as long as the actual variable (int in this case) is one of the intrinsic base-types this is save to do in C and C++.
(If the variable is a class/struct it will probably still work in C, but in C++ all bets are off.)
Why you would want to do this....
For the 1st variant: If your code relies on this sort of messing about it will be error-prone and hard to maintain in the long run.
I can see a some use for the second variant when performance optimizing loops over 2D arrays by replacing them by a 1D pointer run over the data-space, but a good optimizing compiler will often do that by itself anyway.
If the body of the loop is so big/complex the compiler can't optimize/replace the loop by a 1D run on it's own, the performance gain by doing it manually will most likely not be significant either.
You're free to reinterpret the memory any way you'd like. As long as the multiple does not exceed the linear memory. You can even move a to 12, 40 and use negative indexes.
The memory referenced by a is both a int[25][80] and a int[2000]. So says the Standard, 3.8p2:
[ Note: The lifetime of an array object starts as soon as storage with proper size and alignment is obtained, and its lifetime ends when the storage which the array occupies is reused or released. 12.6.2 describes the lifetime of base and member subobjects. — end note ]
a has a particular type, it is an lvalue of type int[25][80]. But p is just int*. It is not "int* pointing into a int[80]" or anything like that. So in fact, the int pointed to is an element of int[25][80] named a, and also an element of int[2000] occupying the same space.
Since p and p+1234 are both elements of the same int[2000] object, the pointer arithmetic is well-defined. And since p[1234] means *(p+1234), it too is well-defined.
The effect of this rule for array lifetime is that you can freely use pointer arithmetic to move through a complete object.
Since std::array got mentioned in the comments:
If one has std::array<std::array<int, 80>, 25> a; then there does not exist a std::array<int, 2000>. There does exist a int[2000]. I'm looking for anything that requires sizeof (std::array<T,N>) == sizeof (T[N]) (and == N * sizeof (T)). Absent that, you have to assume that there could be gaps which mess up traversal of nested std::array.