C++ understanding pointers - c++

I have a bit of code that I'm trying to analyze to study up on pointers and I'd like to ask for some help on whether I'm interpreting it correctly and if I'm not can someone help interpret it for me because I still find pointers to be confusing. You will be able to see my interpretations right after the //.
int main()
{
int x = 5;
int y = 3;
int a[] = {5, 10, 15};
PointerMystery(&x, y, a);
cout << x << " " << y << endl;
for (int i = 0; i < 3; i++) {
cout << a[i] << " ";
}
cout << endl;
return 0;
}
void PointerMystery(int *pa, int b, int c[]) {
// *pc will return array c
int *pc = c;
// *pa which returns the address of x will now make x = 20.
*pa = 20;
// b which was 3 is now equal to 15, therefore y = 15
b = 15;
// *pc which returned a array is now 15(im not so sure about this?)
*pc = b;
// im not sure what is happening here either?
pc += 2;
// *pc will return the value (20) - 10?
*pc = *pa - 10;
cout << *pa << " " << b << " " << *pc << endl;
for (int i = 0; i < 3; i++) {
cout << c[i] << " ";
}
cout << endl;
}

Pointer variables are just what their names implies, variables that point to something.
It might be easier to understand if you think about it graphically, and step by step. So...
When the PointerMystery function is called you have the variables pa, b and c:
+----+ +---+
| pa | --> | 5 |
+----+ +---+
+---+
| 3 |
+---+
+---+ +----+----+----+
| c | --> | 5 | 10 | 15 |
+---+ +----+----+----+
After you assign pc you have
+---+
| c | --\
+---+ \ +----+----+----+
>-> | 5 | 10 | 15 |
+----+ / +----+----+----+
| pc | -/
+----+
You then do *pa = 20; so you have
+----+ +----+
| pa | --> | 20 |
+----+ +----+
Then you do *pc = b;, where b is 15, and since pc really just points to the first element of the passed array that mean you change the first element:
+---+
| c | --\
+---+ \ +----+----+----+
>-> | 15 | 10 | 15 |
+----+ / +----+----+----+
| pc | -/
+----+
You then increase the pointer pc to point to the third element of the passed array:
+---+ +----+----+----+
| c | --> | 15 | 10 | 15 |
+---+ +----+----+----+
^
+----+ |
| pc | -------------/
+----+
You then change the value where pc is pointing to the value of where pa is pointing minus 10, and the value of where pa is pointing is 20 which gives 10 after the subtraction:
+---+ +----+----+----+
| c | --> | 15 | 10 | 10 |
+---+ +----+----+----+
^
+----+ |
| pc | -------------/
+----+
Other things that might be good to know about pointers, is that arrays decays to pointers to their first element when you pass them to a function. That's the reason that c is a pointer and where it points.
Also, you can use both array indexing syntax and pointer syntax for both arrays and pointer. You can do this for arrays since they decays to pointers to their first element, and for pointers as a short-hand for *(ptr + x). In fact, for both pointers and arrays, *(ptr_or_array + x) is equivalent to ptr_or_array[x].
The last bit also explains why adding two to the pc pointer makes it points to the third element, *(pc + 0) is equivalent to pc[0] and *(pc + 2) is equivalent to pc[2], which since array indexes are zero based is the third element.

Related

Fast algorithm for computing intersections of N sets

I have n sets A0,A2,...An-1 holding items of a set E.
I define a configuration C as the integer made of n bits, so C has values between 0 and 2^n-1. Now, I define the following:
(C) an item e of E is in configuration C
<=> for each bit b of C, if b==1 then e is in Ab, else e is not in Ab
For instance for n=3, the configuration C=011 corresponds to items of E that are in A0 and A1 but NOT in A2 (the NOT is important)
C[bitmap] is the count of elements that have exactly that presence/absence pattern in the sets. C[001] is the number of elements in A0 that aren't also in any other sets.
Another possible definition is :
(V) an item e of E is in configuration V
<=> for each bit b of V, if b==1 then e is in Ab
For instance for n=3, the (V) configuration V=011 corresponds to items of E that are in A0 and A1
V[bitmap] is the count of the intersection of the selected sets. (i.e. the count of how many elements are in all of the sets where the bitmap is true.) V[001] is the number of elements in A0. V[011] is the number of elements in A0 and A1, regardless of whether or not they're also in A2.
In the following, the first picture shows items of sets A0, A1 and A2, the second picture shows size of (C) configurations and the third picture shows size of (V) configurations.
I can also represent the configurations by either of two vectors:
C[001]= 5 V[001]=14
C[010]=10 V[010]=22
C[100]=11 V[100]=24
C[011]= 2 V[011]= 6
C[101]= 3 V[101]= 7
C[110]= 6 V[110]=10
C[111]= 4 V[111]= 4
What I want is to write a C/C++ function that transforms C into V as efficiently as possible. A naive approach could be the following 'transfo' function that is obviously in O(4^n) :
#include <vector>
#include <cstdio>
using namespace std;
vector<size_t> transfo (const vector<size_t>& C)
{
vector<size_t> V (C.size());
for (size_t i=0; i<C.size(); i++)
{
V[i] = 0;
for (size_t j=0; j<C.size(); j++)
{
if ((j&i)==i) { V[i] += C[j]; }
}
}
return V;
}
int main()
{
vector<size_t> C = {
/* 000 */ 0,
/* 001 */ 5,
/* 010 */ 10,
/* 011 */ 2,
/* 100 */ 11,
/* 101 */ 3,
/* 110 */ 6,
/* 111 */ 4
};
vector<size_t> V = transfo (C);
for (size_t i=1; i<V.size(); i++) { printf ("[%2ld] C=%2ld V=%2ld\n", i, C[i], V[i]); }
}
My question is : is there a more efficient algorithm than the naive one for transforming a vector C into a vector V ? And what would be the complexity of such a "good" algorithm ?
Note that I could be interested by any SIMD solution.
Well, you are trying to compute 2n values, so you cannot do better than O(2n).
The naive approach starts from the observation that V[X] is obtained by fixing all the 1 bits in X and iterating over all the possible values where the 0 bits are. For example,
V[010] = C[010] + C[011] + C[110] + C[111]
But this approach performs O(2n) additions for every element of V, yielding a total complexity of O(4n).
Here is an O(n × 2n) algorithm. I too am curious if an O(2n) algorithm exists.
Let n = 4. Let us consider the full table of V versus C. Each line in the table below corresponds to one value of V and this value is calculated by summing up the columns marked with a *. The layout of * symbols can be easily deduced from the naive approach.
|0000|0001|0010|0011|0100|0101|0110|0111||1000|1001|1010|1011|1100|1101|1110|1111
0000| * | * | * | * | * | * | * | * || * | * | * | * | * | * | * | *
0001| | * | | * | | * | | * || | * | | * | | * | | *
0010| | | * | * | | | * | * || | | * | * | | | * | *
0011| | | | * | | | | * || | | | * | | | | *
0100| | | | | * | * | * | * || | | | | * | * | * | *
0101| | | | | | * | | * || | | | | | * | | *
0110| | | | | | | * | * || | | | | | | * | *
0111| | | | | | | | * || | | | | | | | *
-------------------------------------------------------------------------------------
1000| | | | | | | | || * | * | * | * | * | * | * | *
1001| | | | | | | | || | * | | * | | * | | *
1010| | | | | | | | || | | * | * | | | * | *
1011| | | | | | | | || | | | * | | | | *
1100| | | | | | | | || | | | | * | * | * | *
1101| | | | | | | | || | | | | | * | | *
1110| | | | | | | | || | | | | | | * | *
1111| | | | | | | | || | | | | | | | *
Notice that the top-left, top-right and bottom-right corners contain identical layouts. Therefore, we can perform some calculations in bulk as follows:
Compute the bottom half of the table (the bottom-right corner).
Add the values to the top half.
Compute the top-left corner.
If we let q = 2n, Thus the recurrent complexity is
T(q) = 2T(q/2) + O(q)
which solves using the Master Theorem to
T(q) = O(q log q)
or, in terms of n,
T(n) = O(n × 2n)
According to the great observation of #CătălinFrâncu, I wrote two recursive implementations of the transformation (see code below) :
transfo_recursive: very straightforward recursive implementation
transfo_avx2 : still recursive but use AVX2 for last step of the recursion for n=3
I propose here that the sizes of the counters are coded on 32 bits and that the n value can grow up to 28.
I also wrote an iterative implementation (transfo_iterative) based on my own observation on the recursion behaviour. Actually, I guess it is close to the non recursive implementation proposed by #chtz.
Here is the benchmark code:
// compiled with: g++ -O3 intersect.cpp -march=native -mavx2 -lpthread -DNDEBUG
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <thread>
#include <algorithm>
#include <sys/times.h>
#include <immintrin.h>
#include <boost/align/aligned_allocator.hpp>
using namespace std;
////////////////////////////////////////////////////////////////////////////////
typedef u_int32_t Count;
// Note: alignment is important for AVX2
typedef std::vector<Count,boost::alignment::aligned_allocator<Count, 8*sizeof(Count)>> CountVector;
typedef void (*callback) (CountVector::pointer C, size_t q);
typedef vector<pair<const char*, callback>> FunctionsVector;
unsigned int randomSeed = 0;
////////////////////////////////////////////////////////////////////////////////
double timestamp()
{
struct timespec timet;
clock_gettime(CLOCK_MONOTONIC, &timet);
return timet.tv_sec + (timet.tv_nsec/ 1000000000.0);
}
////////////////////////////////////////////////////////////////////////////////
CountVector getRandomVector (size_t n)
{
// We use the same seed, so we'll get the same random values
srand (randomSeed);
// We fill a vector of size q=2^n with random values
CountVector C(1ULL<<n);
for (size_t i=0; i<C.size(); i++) { C[i] = rand() % (1ULL<<(8*sizeof(Count))); }
return C;
}
////////////////////////////////////////////////////////////////////////////////
void copy_add_block (CountVector::pointer C, size_t q)
{
for (size_t i=0; i<q/2; i++) { C[i] += C[i+q/2]; }
}
////////////////////////////////////////////////////////////////////////////////
void copy_add_block_avx2 (CountVector::pointer C, size_t q)
{
__m256i* target = (__m256i*) (C);
__m256i* source = (__m256i*) (C+q/2);
size_t imax = q/(2*8);
for (size_t i=0; i<imax; i++)
{
target[i] = _mm256_add_epi32 (source[i], target[i]);
}
}
////////////////////////////////////////////////////////////////////////////////
// Naive approach : O(4^n)
////////////////////////////////////////////////////////////////////////////////
CountVector transfo_naive (const CountVector& C)
{
CountVector V (C.size());
for (size_t i=0; i<C.size(); i++)
{
V[i] = 0;
for (size_t j=0; j<C.size(); j++)
{
if ((j&i)==i) { V[i] += C[j]; }
}
}
return V;
}
////////////////////////////////////////////////////////////////////////////////
// Recursive approach : O(n.2^n)
////////////////////////////////////////////////////////////////////////////////
void transfo_recursive (CountVector::pointer C, size_t q)
{
if (q>1)
{
transfo_recursive (C+q/2, q/2);
transfo_recursive (C, q/2);
copy_add_block (C, q);
}
}
////////////////////////////////////////////////////////////////////////////////
// Iterative approach : O(n.2^n)
////////////////////////////////////////////////////////////////////////////////
void transfo_iterative (CountVector::pointer C, size_t q)
{
size_t i = 0;
for (size_t n=q; n>1; n>>=1, i++)
{
size_t d = 1<<i;
for (ssize_t j=q-1-d; j>=0; j--)
{
if ( ((j>>i)&1)==0) { C[j] += C[j+d]; }
}
}
}
////////////////////////////////////////////////////////////////////////////////
// Recursive AVX2 approach : O(n.2^n)
////////////////////////////////////////////////////////////////////////////////
#define ROTATE1(s) _mm256_permutevar8x32_epi32 (s, _mm256_set_epi32(0,7,6,5,4,3,2,1))
#define ROTATE2(s) _mm256_permutevar8x32_epi32 (s, _mm256_set_epi32(0,0,7,6,5,4,3,2))
#define ROTATE4(s) _mm256_permutevar8x32_epi32 (s, _mm256_set_epi32(0,0,0,0,7,6,5,4))
void transfo_avx2 (CountVector::pointer V, size_t N)
{
__m256i k1 = _mm256_set_epi32 (0,0xFFFFFFFF,0,0xFFFFFFFF,0,0xFFFFFFFF,0,0xFFFFFFFF);
__m256i k2 = _mm256_set_epi32 (0,0,0xFFFFFFFF,0xFFFFFFFF,0,0,0xFFFFFFFF,0xFFFFFFFF);
__m256i k4 = _mm256_set_epi32 (0,0,0,0,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF);
if (N==8)
{
__m256i* source = (__m256i*) (V);
*source = _mm256_add_epi32 (*source, _mm256_and_si256(ROTATE1(*source),k1));
*source = _mm256_add_epi32 (*source, _mm256_and_si256(ROTATE2(*source),k2));
*source = _mm256_add_epi32 (*source, _mm256_and_si256(ROTATE4(*source),k4));
}
else // if (N>8)
{
transfo_avx2 (V+N/2, N/2);
transfo_avx2 (V, N/2);
copy_add_block_avx2 (V, N);
}
}
#define ROTATE1_AND(s) _mm256_srli_epi64 ((s), 32) // odd 32bit elements
#define ROTATE2_AND(s) _mm256_bsrli_epi128 ((s), 8) // high 64bit halves
// gcc doesn't have _mm256_zextsi128_si256
// and _mm256_castsi128_si256 doesn't guarantee zero extension
// vperm2i118 can do the same job as vextracti128, but is slower on Ryzen
#ifdef __clang__ // high 128bit lane
#define ROTATE4_AND(s) _mm256_zextsi128_si256(_mm256_extracti128_si256((s),1))
#else
//#define ROTATE4_AND(s) _mm256_castsi128_si256(_mm256_extracti128_si256((s),1))
#define ROTATE4_AND(s) _mm256_permute2x128_si256((s),(s),0x81) // high bit set = zero that lane
#endif
void transfo_avx2_pcordes (CountVector::pointer C, size_t q)
{
if (q==8)
{
__m256i* source = (__m256i*) (C);
__m256i tmp = *source;
tmp = _mm256_add_epi32 (tmp, ROTATE1_AND(tmp));
tmp = _mm256_add_epi32 (tmp, ROTATE2_AND(tmp));
tmp = _mm256_add_epi32 (tmp, ROTATE4_AND(tmp));
*source = tmp;
}
else //if (N>8)
{
transfo_avx2_pcordes (C+q/2, q/2);
transfo_avx2_pcordes (C, q/2);
copy_add_block_avx2 (C, q);
}
}
////////////////////////////////////////////////////////////////////////////////
// Template specialization (same as transfo_avx2_pcordes)
////////////////////////////////////////////////////////////////////////////////
template <int n>
void transfo_template (__m256i* C)
{
const size_t q = 1ULL << n;
transfo_template<n-1> (C);
transfo_template<n-1> (C + q/2);
__m256i* target = (__m256i*) (C);
__m256i* source = (__m256i*) (C+q/2);
for (size_t i=0; i<q/2; i++)
{
target[i] = _mm256_add_epi32 (source[i], target[i]);
}
}
template <>
void transfo_template<0> (__m256i* C)
{
__m256i* source = (__m256i*) (C);
__m256i tmp = *source;
tmp = _mm256_add_epi32 (tmp, ROTATE1_AND(tmp));
tmp = _mm256_add_epi32 (tmp, ROTATE2_AND(tmp));
tmp = _mm256_add_epi32 (tmp, ROTATE4_AND(tmp));
*source = tmp;
}
void transfo_recur_template (CountVector::pointer C, size_t q)
{
#define CASE(n) case 1ULL<<n: transfo_template<n> ((__m256i*)C); break;
q = q / 8; // 8 is the number of 32 bits items in the AVX2 registers
// We have to 'link' the dynamic value of q with a static template specialization
switch (q)
{
CASE( 1); CASE( 2); CASE( 3); CASE( 4); CASE( 5); CASE( 6); CASE( 7); CASE( 8); CASE( 9);
CASE(10); CASE(11); CASE(12); CASE(13); CASE(14); CASE(15); CASE(16); CASE(17); CASE(18); CASE(19);
CASE(20); CASE(21); CASE(22); CASE(23); CASE(24); CASE(25); CASE(26); CASE(27); CASE(28); CASE(29);
default: printf ("transfo_template undefined for q=%ld\n", q); break;
}
}
////////////////////////////////////////////////////////////////////////////////
// Recursive approach multithread : O(n.2^n)
////////////////////////////////////////////////////////////////////////////////
void transfo_recur_thread (CountVector::pointer C, size_t q)
{
std::thread t1 (transfo_recur_template, C+q/2, q/2);
std::thread t2 (transfo_recur_template, C, q/2);
t1.join();
t2.join();
copy_add_block_avx2 (C, q);
}
////////////////////////////////////////////////////////////////////////////////
void header (const char* title, const FunctionsVector& functions)
{
printf ("\n");
for (size_t i=0; i<functions.size(); i++) { printf ("------------------"); } printf ("\n");
printf ("%s\n", title);
for (size_t i=0; i<functions.size(); i++) { printf ("------------------"); } printf ("\n");
printf ("%3s\t", "# n");
for (auto fct : functions) { printf ("%20s\t", fct.first); }
printf ("\n");
}
////////////////////////////////////////////////////////////////////////////////
// Check that alternative implementations provide the same result as the naive one
////////////////////////////////////////////////////////////////////////////////
void check (const FunctionsVector& functions, size_t nmin, size_t nmax)
{
header ("CHECK (0 values means similar to naive approach)", functions);
for (size_t n=nmin; n<=nmax; n++)
{
printf ("%3ld\t", n);
CountVector reference = transfo_naive (getRandomVector(n));
for (auto fct : functions)
{
// We call the (in place) transformation
CountVector C = getRandomVector(n);
(*fct.second) (C.data(), C.size());
int nbDiffs= 0;
for (size_t i=0; i<C.size(); i++)
{
if (reference[i]!=C[i]) { nbDiffs++; }
}
printf ("%20ld\t", nbDiffs);
}
printf ("\n");
}
}
////////////////////////////////////////////////////////////////////////////////
// Performance test
////////////////////////////////////////////////////////////////////////////////
void performance (const FunctionsVector& functions, size_t nmin, size_t nmax)
{
header ("PERFORMANCE", functions);
for (size_t n=nmin; n<=nmax; n++)
{
printf ("%3ld\t", n);
for (auto fct : functions)
{
// We compute the average time for several executions
// We use more executions for small n values in order
// to have more accurate results
size_t nbRuns = 1ULL<<(2+nmax-n);
vector<double> timeValues;
// We run the test several times
for (size_t r=0; r<nbRuns; r++)
{
// We don't want to measure time for vector fill
CountVector C = getRandomVector(n);
double t0 = timestamp();
(*fct.second) (C.data(), C.size());
double t1 = timestamp();
timeValues.push_back (t1-t0);
}
// We sort the vector of times in order to get the median value
std::sort (timeValues.begin(), timeValues.end());
double median = timeValues[timeValues.size()/2];
printf ("%20lf\t", log(1000.0*1000.0*median)/log(2));
}
printf ("\n");
}
}
////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char* argv[])
{
size_t nmin = argc>=2 ? atoi(argv[1]) : 14;
size_t nmax = argc>=3 ? atoi(argv[2]) : 28;
// We get a common random seed
randomSeed = time(NULL);
FunctionsVector functions = {
make_pair ("transfo_recursive", transfo_recursive),
make_pair ("transfo_iterative", transfo_iterative),
make_pair ("transfo_avx2", transfo_avx2),
make_pair ("transfo_avx2_pcordes", transfo_avx2_pcordes),
make_pair ("transfo_recur_template", transfo_recur_template),
make_pair ("transfo_recur_thread", transfo_recur_thread)
};
// We check for some n that alternative implementations
// provide the same result as the naive approach
check (functions, 5, 15);
// We run the performance test
performance (functions, nmin, nmax);
}
And here is the performance graph:
One can observe that the simple recursive implementation is pretty good, even compared to the AVX2 version. The iterative implementation is a little bit disappointing but I made no big effort to optimize it.
Finally, for my own use case with 32 bits counters and for n values up to 28, these implementations are obviously ok for me compared to the initial "naive" approach in O(4^n).
Update
Following some remarks from #PeterCordes and #chtz, I added the following implementations:
transfo-avx2-pcordes : the same as transfo-avx2 with some AVX2 optimizations
transfo-recur-template : the same as transfo-avx2-pcordes but using C++ template specialization for implementing recursion
transfo-recur-thread : usage of multithreading for the two initial recursive calls of transfo-recur-template
Here is the updated benchmark result:
A few remarks about this result:
the AVX2 implementations are logically the best options but maybe not with the maximum potential x8 speedup with counters of 32 bits
among the AVX2 implementations, the template specialization brings a little speedup but it almost fades for bigger values for n
the simple two-threads version has bad results for n<20; for n>=20, there is always a little speedup but far from a potential 2x.

how can these 2 pointers have the same value?

I have a problem with this Code.The answer is 26 14 26 26.
Problem is, i keep finding x as 13.After the first fun function x and xptr becomes 13 but I can not figure out how it becomes 26.
#include <iostream>
using namespace std;
int* fun(int *a){
*a = *a +5;
return a;
}
int main(){
int x, y, *xpntr, *ypntr;
x = 3;
xpntr = &x;
fun(xpntr);
y = 10 + (*xpntr / 2);
ypntr = fun(xpntr);
*ypntr = *xpntr + *ypntr;
cout << x << " " << y << " " << *xpntr << " " << *ypntr << endl;
}
Before your ypntr = fun(xpntr); line, x=8, and xpntr points to x.
Now's the tricky part: ypntr = fun(xpntr) changes the value of x to 13. And fun is returning a reference to x -- ypntr now points to x as well.
This means that both xptr and yptr will point to the same data.
Your last statement (*ypntr = *xpntr + *ypntr) simply doubles the value of x.
+---------------------------+----+----+------+------+
| code | x | y | xptr | yptr |
+---------------------------+----+----+------+------+
| x = 3; | 3 | ? | ? | ? |
| xpntr = &x; | 3 | ? | &x | ? |
| fun(xpntr); | 8 | ? | &x | ? |
| y = 10 + (*xpntr / 2); | 8 | 14 | &x | ? |
| ypntr = fun(xpntr); | 13 | 14 | &x | &x |
| *ypntr = *xpntr + *ypntr; | 26 | 14 | &x | &x |
+---------------------------+----+----+------+------+
Let's go through your code step by step
#include <iostream>
using namespace std;
int* fun(int *a){
The next line means: "get the value from the location where pointer a is pointing to, add 5 to it, and write it to the location pointer a is pointing to." This does not change the pointer itself!
*a = *a +5;
Next return the pointer a, which is just the same value as was passed to the function
return a;
}
int main(){
int x, y, *xpntr, *ypntr;
x = 3;
Assign to xpntr the memory location of x
xpntr = &x;
Call fun with xpntr, discard what it returns. x will then be 3+5 = 8
fun(xpntr);
Assign to y the result of 10 + ( value of what xpntr is pointing to / 2) – xpntr isn't changed by this. y = 10 + (8/2) = 14
y = 10 + (*xpntr / 2);
Call fun with xpntr, something happens to what xpntr is pointing to, but as we already learnt, xpntr itself is not changed in the process. The value returned by fun (which is just xpntr) is assigned to ypntr. x will be changed again to 8 + 5 = 13
ypntr = fun(xpntr);
Assign to where ypntr is pointing to (which is right now identical to xpntr to the sum of what's pointed to by xpntr and ypntr. So x will then be 13 + 13 = 26 ← this is where your 26 comes from!
*ypntr = *xpntr + *ypntr;
Print the values of where xpntr and ypntr point to.
cout << x << " " << y << " " << *xpntr << " " << *ypntr << endl;
}

Multidimensional array

The question is to find the output of the follwing program.
This came out in my test and i got wrong. My answer was 4, 7, 10. The answer is 4,8,12 but i need an explanation on how it works
#include<iostream>
using namespace std;
int main ()
{
int number = 4;
int array[] = {7,8,9,10,11,12,13};
int *p1 = &number ;
int *p2 = array;
int *p3 = &array[3];
int *q[] = {p1,p2,p3};
cout << q[0][0] << endl ;
cout << q[1][1] << endl ;
cout << q[2][2] << endl ;
return 0;
}
What you have is not a multi-dimensional array (C++ doesn't really have it). What you have is an array of pointers. And pointers can be indexed like arrays.
In "graphic" form the array q looks something like this:
+------+------+------+
| q[0] | q[1] | q[2] |
+------+------+------+
| | |
v | v
+------+ | +-----+----------+----------+-----+
|number| | | ... | array[3] | array[4] | ... |
+------+ | +-----+----------+----------+-----+
v
+----------+----------+-----+
| array[0] | array[1] | ... |
+----------+----------+-----+
Some notes:
What most people call multidimensional arrays are actually arrays of arrays. Much like you can have an array of integers, or like in the case of q in your code an array of pointers to integers, one can also have an array of arrays of integers. For more "dimensions" it's just another nesting of arrays.
As for why pointers and arrays both can be indexed the same way, it's because for any array or pointer a and (valid) index i, the expressions a[i] is equal to *(a + i). This equality is also the reason you can use an array as a pointer to its first element. To get a pointer to an arrays first element one can write &a[0]. It's equal to &*(a + 0), where the address-of and dereference operators cancel each other out leading to (a + 0) which is the same as (a) which is the same as a. So &a[0] and a are equal.
q[0] is in fact p1, q[1] is in fact p2 and q[2] is p3.
Now p1 is the address of number so p1[0] is simply the value of number which you correctly computed to be 4.
p2 points to array so p2[1] is the same as array[1] which is the second element in array ot 8.
p3 points to the subarray of array starting from position 3. So p3[2] is the same as array[3 + 2] = array[5] = 12.

what is wrong when i'm trying to access this array (2d ) in c++

below is the code that I'm trying to run
#include <iostream>
using namespace std;
int foo(int **a){
cout<<*(a+3)<<endl;
return 1;
}
int main(){
int a[2][2] = {{1,2},{3,4}};
std::cout << sizeof(a);
foo((int **)a);
}
when I have four elements in this array, shouldn't the value *(a+3) return a value 4, instead of that its returning an address and when i try to dereference that address (i.e. **(a+3)) i get segfault 11
Actually you are defining an array of arrays of integers. It can decay to a pointer to an array of integers, but it will not decay into a pointe to a pointer of integers.
It will help if you draw the memory layout:
+---------+---------+---------+---------+
| a[0][0] | a[0][1] | a[1][0] | a[1][1] |
+---------+---------+---------+---------+
| 1 | 2 | 3 | 4 |
+---------+---------+---------+---------+
If you let it decay into a pointer-to-array-of-integer:
int (*pa)[2] = a;
+---------+---------+---------+---------+
| pa[0] | pa[1] |
+---------+---------+---------+---------+
| 1 | 2 | 3 | 4 |
+---------+---------+---------+---------+
Note how sizeof(*pa) = 2 * sizeof(int). Each of these values can decay into a pointer to integer, but never into a pointer to a pointer:
int *p = pa[0];
Anyway, you can cast the decayed pointer-to-array-of-integer into a pointer to integers and access the four values directly:
int *p = (int*)a;
std::cout << p[3] << std::endl;
std::cout << *(p + 3) << std::endl;
The memory will be like this:
+---------+---------+---------+---------+
| p[0] | p[1] | p[2] | p[3] |
+---------+---------+---------+---------+
| 1 | 2 | 3 | 4 |
+---------+---------+---------+---------+
But if you cast it into a int** you will get meaningless values, because in memory there are no pointers, just integers.
A array is not a pointer. Yes it can decay to a pointer that does not mean it is one. If you want to pass a 2d array then you can use
int foo(int a[][2]);
or if you want to take any size array then you can use
template<std::size_t N>
int foo(int a[][N]);
You are dereferencing too far. a already points to the first element in the array. So **a treats the first value in the array as an address instead of a value. Try
foo((int *)a)
When you dereference int** you get int*. However, you are dereferencing a location with a value 4 that is interpreted as int*. That's why cout prints 0x4, because it formats it as an address. Try taking int* as parameter:
#include <iostream>
using namespace std;
int foo(int *a){
cout<<*(a+3)<<endl;
return 1;
}
int main(){
int a[2][2] = {{1,2},{3,4}};
std::cout << sizeof(a);
foo((int *)a);
}
It prints 4 then.
This is just an example of how to fix up this piece of code in particular, and is not the right way of doing things. Also as #rodrigo stated in the comments of his answer, if the size of an int is not equal to the size of its pointer, this is not going to work. I just wanted to show you why you thought it was printing an address - because it is being interpreted as one.

Wrong output of reference to pointer to int

I am trying to use a reference to pointer to int like in below program. But I am not getting the expected output.
Output:
9 5
5 9
Expecting:
9 5
9 5
Code:
#include <iostream>
using namespace std;
void swap (int *& a, int *&b)
{
int *t = a;
a = b;
b = t;
}
int main()
{
int a = 5, b = 9;
int *p = &a;
int *q = &b;
swap (p, q);
cout << *p << " " << *q << endl;
cout << a << " " << b << endl;
return 0;
}
Why is my expectation wrong? I head that reference is nothing just an other name of the target variable.
You swap the pointers, not the values. Your expectation is wrong.
You're swapping the values of the pointers.
Look at this illustration:
First, p is pointing at a, q is pointing at b:
p a
+---+ +---+
+ ------> | 5 |
+---+ +---+
q b
+---+ +---+
+ ------> | 9 |
+---+ +---+
After you swap p and q, q is pointing at a, and p is pointing at b:
q a
+---+ +---+
+ ------> | 5 |
+---+ +---+
p b
+---+ +---+
+ ------> | 9 |
+---+ +---+
But both a and b still have their old values.
In your case, you're swapping the pointers (and not the values). Since you're swapping the pointers, the actual values inside a and b remain unchanged. Hencewhy when you print the value of a, you get 5 and when you print b you get 9.
In general, you are swapping two pointers and not what they point to.
Maybe your function is confusing you as you are calling the values a and b. Your function swaps two pointers such that the first one now points where the second one was pointing, and the second points to where the first was pointing.
p was previously pointing to a and q to b. Now p points to b and q points to a thus your output is 9 5 5 9
There are references in your swap function. These are references to the pointers, so a and b in the function scope become aliases to the parameters passed in, i.e. p and q and thus modify p and q (to point elsewhere).
I head that reference is nothing just a other name of the target
variable
Yes, and target variable in your case is a pointer of the variable, not the variable. Because you're using * in the function declaration. Then, you're swaping pointers not values.
Use one of these ways:
void swap(int &a, int &b)
{
int t=a;
a=b;
b=t;
}
// ...
int a=5,b=9;
swap(a,b);
or
void swap(int *a, int *b)
{
int t=*a;
*a=*b;
*b=t;
}
// ...
int a=5,b=9;
swap(&a,&b);