So im having this problem that I want to add "one" structure and one int to my shared memory
And I want to have my "int in the first position of the shared memory" (since i ll need this int in another programs) and then have the structure
This is my code
int id = shmget( 0x82488, (sizeof(student)) + sizeof(int) ,IPC_CREAT | 0666 );
exit_on_error (id, "Error");
int *p = shmat(id,0,0);
exit_on_null(p,"Erro no attach");
Student *s = shmat(id,0,0);
exit_on_null (s,"Error");
And now comes my question since I have 2 pointers how can I make the int be the first and then the structure, should I just
p[0]=100 s[1] = (new Student)
I would just do
int *p = shmat(id,0,0);
exit_on_null(p,"Erro no attach");
Student *s = (Student*)(void*)(p + 1);
so that s points to where the next int would be if that would be an int.
It is a bit tricky, but clears all possible interoperation issues with possible padding bytes in a struct.
Example:
+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---+---+---+---+---+---+---+---+---+---+
In this case, p points to the location 0 (relative to the start of the buffer), and thus p + 1 points to the position 4 (if an int has 32 bits). Casting p + 1 the way I do makes pont s to this place, but be of type Student *.
And if you want to add a structure struct extension, you do the same:
struct extension *x = (struct extension*)(void*)(s + 1);
This points immediately behind the Struct and, again, has the correct pointer type.
Related
A question ask me if this code, contains any error.
The compiler doesn't give me any errors but this code contains some arguments that I don't know
The code is this:
int* mycalloc(int n) {
int *p = malloc(n*sizeof(int)), *q; //what does the ", *q"?
for (q=p; q<=p+n; ++q) *q = 0;
return p;
}
The possible solutions are:
The program is correct
There is an error at line 1
There is an error at line 2
There is an error at line 3
There is an error at line 4
There is no compile time error in the above code but at run time it will crash because of q<=p+n. q is simply an integer pointer.
It should be
for (q=p; q<p+n; ++q) /** it works even though n is zero or you can add seperate if condition for the same, this may be the interviewer concern **/
*q = 0;
What mymalloc is doing is allocating space for n integers and initialize
them with 0.
This could have been done so:
int *mymalloc(size_t n)
{
int *arr = malloc(n * sizeof *arr);
if(arr == NULL)
return NULL;
memset(arr, 0, n * sizeof *arr);
return arr;
}
or better
int *mymalloc(size_t n)
{
return calloc(n, sizeof int);
}
The way your function is doing this, is by looping through the array using a
pointer q. Let me explain
int *p = malloc(n*sizeof(int)), *q;
It declares two int* (pointers to int) variables p and q. p is
initialized with the values returned by malloc and q is left uninitialized.
It's the same as doing:
int *p;
int *q;
p = malloc(n*sizeof(int));
but in one line.
The next part is the interesting one:
for (q=p; q<p+n; ++q)
*q = 0;
First I corrected the condition and wrote it in two lines.
You can read this loop as follows:
initialize q with the same value as p, i.e. p and q point to the start
of the allocated memory
end the loop when q is pointing beyond the allocated memory
at the end of the loop, do ++q, which makes q go to the next int
In the loop do *q = 0, equivalent to q[0] = 0, thus setting the integer to
0 that is pointed to by q.
Let us think about the memory layout. Let's say n = 5. In my graphic ?
represents an unkown value.
BEFORE THE LOOP
b = the start of the allocated memory aka malloc return value
si = size of an integer in bytes, mostly 4
(beyond the limits)
b+0 b+1*si b+2*si b+3*si b+4*si b+5*si
+---------+---------+---------+---------+---------+
| ???? | ???? | ???? | ???? | ???? |
+---------+---------+---------+---------+---------+
^
|
p
In the first loop, q is set to p and *q = 0 is executed. It's the same as
doing p[0] = 0.
FIRST ITERATION
b = the start of the allocated memory aka malloc return value
si = size of an integer in bytes, mostly 4
(beyond the limits)
b+0 b+1*si b+2*si b+3*si b+4*si b+5*si
+---------+---------+---------+---------+---------+
| 0 | ???? | ???? | ???? | ???? |
+---------+---------+---------+---------+---------+
^
|
p,q
This is how the memory would look like after *q=0. Then the next loop is
executed, but before of that q++ is executed
BEFORE SECOND ITERATION, `q++`
b = the start of the allocated memory aka malloc return value
si = size of an integer in bytes, mostly 4
(beyond the limits)
b+0 b+1*si b+2*si b+3*si b+4*si b+5*si
+---------+---------+---------+---------+---------+
| 0 | ???? | ???? | ???? | ???? |
+---------+---------+---------+---------+---------+
^ ^
| |
p q
Now *q = 0 is executed, which is the same as p[1] = 0:
SECOND ITERATION,
b = the start of the allocated memory aka malloc return value
si = size of an integer in bytes, mostly 4
(beyond the limits)
b+0 b+1*si b+2*si b+3*si b+4*si b+5*si
+---------+---------+---------+---------+---------+
| 0 | 0 | ???? | ???? | ???? |
+---------+---------+---------+---------+---------+
^ ^
| |
p q
Then the loop continues, and you get now the point. This is why in your code the
condition of the loop q <= p+n is wrong, because it would do 1 step farther
than it needs and would write a 0 beyond the limits.
You loop is using pointer arithmetic. Pointer arithmetic is similar to regular
arithmetic (i.e. addition, subtraction with natural numbers), but it takes
the size of the object in consideration.
Consider this code
int p[] = { 1, 2, 3, 4, 5};
int *q = p;
p is an array of int of dimension 5. The common size of int is 4, that
means that the array q needs 20 bytes of memory. The first 4 bytes are for
p[0], the next 4 for p[1], etc. q is a pointer to int pointing at the
first element of the array p. In fact this code is equivalent to
int p[] = { 1, 2, 3, 4, 5};
int *q = &(p[0]);
That is what people call array decay, meaning that you can access the array as
if where a pointer. For pointer arithmetic there is almost no distinction
between the two.
What is pointer arithmetic then?
This: p+2. This will get you a pointer that is 2 spaces after p. Note that
I'm using the word space and not byte, and that's because depending on the type
of p, the number of bytes will be different. Mathematically what the compiler
is doing is calculating the address from
address where p is pointing + 2x(number of bytes for an int)
because the compiler knows the type of the pointer.
That's why you can also have expressions like p++ when p is a pointer. It is
doing p = p + 1 which is p = &(p[1]);.
b is the base address where the memory starts
memory
address b+0 b+1*si b+2*si b+3*si b+4*si
+---------+---------+---------+---------+---------+
| 1 | 2 | 3 | 4 | 5 |
+---------+---------+---------+---------+---------+
p p+1 p+2 p+3 p+4
pointer
(pointer arithmetic)
import cv2
import numpy as np
import scipy.ndimage
from sklearn.externals import joblib
from tools import *
#from ml import *
import argparse
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import confusion_matrix
from sklearn.externals import joblib
from sklearn import svm
import numpy as np
import os
import cv2
parser = argparse.ArgumentParser()
parser.add_argument('--mode', '-mode', help="Mode : train or predict", type=str)
parser.add_argument('--a', '-algorithm', help="algorithm/model name", type=str)
parser.add_argument('--i', '-image', help="licence plate to read", type=str)
parser.add_argument('--model', '-model', help="Model file path", type=str)
#parser.add_argument('--d', '-dataset', help="dataset folder path", type=str)
While experimenting with methods for stepping through an array of strings in C, I developed the following small program:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char* string;
int main() {
char *family1[4] = {"father", "mother", "son", NULL};
string family2[4] = {"father", "mother", "son", NULL};
/* Loop #1: Using a simple pointer to step through "family1". */
for (char **p = family1; *p != NULL; p++) {
printf("%s\n", *p);
}
putchar('\n');
/* Loop #2: Using the typedef for clarity and stepping through
* family2. */
for (string *s = family2; *s != NULL; s++) {
printf("%s\n", *s);
}
putchar('\n');
/* Loop #3: Again, we use the pointer, but with a unique increment
* step in our for loop. This fails to work. Why? */
for (string s = family2[0]; s != NULL; s = *(&s + 1)) {
printf("%s\n", s);
}
}
My specific question involves the failure of Loop #3. When run through the debugger, Loops #1 and #2 complete successfully, but the last loop fails for an unknown reason. I would not have asked this here, except for the fact that is shows me that I have some critical misunderstanding regarding the "&" operator.
My question (and current understanding) is this: family2 is an array-of-pointer-to-char. Thus, when s is set to family2[0] we have a (char*) pointing to "father". Therefore, taking &s should give us the equivalent of family2, pointing to the first element of family2 after the expected pointer decay. Why doesn't, then,
*(&s + 1) point to the next element, as expected?
Many thanks,
lifecrisis
EDIT -- Update and Lessons Learned:
The following list is a summary of all of the relevant facts and interpretations that explain why the third loop does not work like the first two.
s is a separate variable holding a copy of the value (a pointer-to-char) from the variable family2[0]. I.e., these two equivalent values are positioned at SEPARATE locations in memory.
family2[0] up to family2[3] are contiguous elements of memory, and s has no presence in this space, though it does contain the same value that is stored in family2[0] at the start of our loop.
These first two facts mean that &s and &family2[0] are NOT equal. Thus, adding one to &s will return a pointer to unknown/undefined data, whereas adding one to &family2[0] will give you &family2[1], as desired.
In addition, the update step in the third for loop doesn't actually result in s stepping forward in memory on each iteration. This is because &s is constant throughout all iterations of our loop. This is the cause of the observed infinite loop.
Thanks to EVERYONE for their help!
lifecrisis
When you do s = *(&s + 1) the variable s is a local variable in an implicit scope that only contains the loop. When you do &s you get the address of that local variable, which is unrelated to any of the arrays.
The difference from the previous loop is that there s is a pointer to the first element in the array.
To explain it a little more "graphically" what you have in the last loop is something like
+----+ +---+ +------------+
| &s | ---> | s | ---> | family2[0] |
+----+ +---+ +------------+
That is, &s is pointing to s, and s is pointing to family2[0].
When you do &s + 1 you effectively have something like
+------------+
| family2[0] |
+------------+
^
|
+---+----
| s | ...
+---+----
^ ^
| |
&s &s + 1
Pictures help a lot:
+----------+
| "father" |
+----------+ +----------+ +-------+ NULL
/-----------→1000 | "mother" | | "son" | ↑
+-----+ ↑ +----------+ +-------+ |
| s | ? | 2000 2500 |
+-----+ | ↑ ↑ |
6000 6008 +----------------+----------------+--------------+--------------+
| family2[0] | family2[1] | family2[2] | family2[3] |
+----------------+----------------+--------------+--------------+
5000 5008 5016 5024
( &s refers to 6000 )
( &s+1 refers to 6008 but )
( *(&s+1) invokes UB )
Addresses chosen as random integers for simplicity
The thing here is that, although both s and family2[0] point to the same base address of the string literal "father", the pointers aren't related with each other and has its own different memory location where they are stored. *(&s+1) != family2[1].
You hit UB when you do *(&s + 1) because &s + 1 is a memory location you're not supposed to tamper with, i.e, it doesn't belong to any object you created. You never know what's stored in there => Undefined Behavior.
Thanks #2501 for pointing out several mistakes!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char* string;
int main() {
char *family1[4] = { "father", "mother", "son", NULL };
string family2[4] = { "father", "mother", "son", NULL };
/* Loop #1: Using a simple pointer to step through "family1". */
for (char **p = family1; *p != NULL; p++) {
printf("%s\n", *p);
}
putchar('\n');
/* Loop #2: Using the typedef for clarity and stepping through
* family2. */
for (string *s = family2; *s != NULL; s++) {
printf("%s\n", *s);
}
putchar('\n');
/* Loop #3: Again, we use the pointer, but with a unique increment
* step in our for loop. This fails to work. Why? */
/*for (string s = family2[0]; s != NULL; s = *(&s + 1)) {
printf("%s\n", s);
}
*/
for (int j = 0; j < 3; j++)
{
printf("%d ",family2[j]);
printf("%d\n", strlen(family2[j]));
}
printf("\n");
int i = 0;
for (string s = family2[i]; i != 3; s = (s + strlen(family2[i]) + 2),i++) {
printf("%d ",s);
printf("%s\n", s);
}
system("pause");
}
this is a example revised from your code,if you run it,you will find the change of the address of the point and the family2, then you will understand the relationship of the loop #3.
I have a generic struct declared and an array of these structs as given below:
struct A
{
int x,y,z;
char a,b,c;
};
struct A *str_arr[5];
From my understanding str_arr is a pointer to a block of memory which stores pointers to the 5 structs in sequential order and therefore these pointers can be accessed via pointer arithmetic or array indexing as:
struct A *str_a = str_arr[1]; // points to 2nd struct?
struct A *str_b = str_arr + 2*sizeof(struct A*); // points to 3rd struct?
However, these 5 structs might not be in sequential memory?
printf("%p\n", str_arr); // prints memory location of start of str_arr pointers?
printf("%p\n", str_arr[1]) // prints memory location of 2nd struct?
printf("%d\n" str_arr == &str_arr[0]) // prints 1?
I would just like clarification that my understanding is correct with all of the points I have raised.
All is correct except one:
struct A **str_b = str_arr + 2 /* *sizeof(struct A*) */;
/* ^^ ^^^^^^^^^^^^^^^^^^^^^^ */
/* Not need to multiply with size. Dereference with * if your type is struct A * */
or
struct A *str_b = *(str_arr + 2);
You give the offset in terms of number of elements and not the size in bytes.
str_arr + 2*sizeof(struct A*) is equivalent to &str_arr[2*sizeof(struct A*)]
+0 +1 +2 +3 +4
+---+---+---+---+---+
| A | B | C | D | E |
+---+---+---+---+---+
str_arr ^^^^^^^^^^^^^^^^^^^^^
&str_arr[0]^^^^
str_arr[1] = B
str_arr is the address of array start
str_arr[1] is the contents at offset +1 i.e. B which is an address pointing to object of type struct A.
str_arr == &str_arr[0] are same address with different type
As suggested by #Gopi, last point can be proven by printing the following:
sizeof str_arr v/s &str_arr[0]
Following address, &str_arr + 1 v/s str_arr + 1
For a course about the functioning of operating systems, we had to write a malloc/free implementation for a specific sized struct. Our idea was to store the overhead, like the start and end of the specified (static) memory block our code has to work in, in the first few addresses of that block.
However, something went wrong with the calculation of the last memory slot; We're adding the size of all usable memory slots to the address of the first usable memory slot, to determine what the last slot is. However, when adding the int sizeofslots to the address of currentslot, it actually adds sizeofslots 4 times to this address. Here's the relevant code:
/* Example memory block*/
/* |------------------------------------------------------------------------------|*/
/* | ovr | 1 t | 0 | 1 t | 1 t | 1 t | 0 | 0 | 0 | 0 | 0 | 0 | 0 |*/
/* |------------------------------------------------------------------------------|*/
/* ovr: overhead, the variables `currentslot`, `firstslot` and `lastslot`.
* 1/0: Whether or not the slot is taken.
* t: the struct
*/
/* Store the pointer to the last allocated slot at the first address */
currentslot = get_MEM_BLOCK_START();
*currentslot = currentslot + 3*sizeof(void *);
/* The first usable memory slot after the overhead */
firstslot = currentslot + sizeof(void *);
*firstslot = currentslot + 3*sizeof(void *);
/* The total size of all the effective memory slots */
int sizeofslots = SLOT_SIZE * numslots;
/* The last usable slot in our memory block */
lastslot = currentslot + 2*sizeof(void*);
*lastslot = firstslot + sizeofslots;
printf("%p + %i = %p, became %p\n", previous, sizeofslots, previous + (SLOT_SIZE*numslots), *lastslot);
We figured it had something to do with integers being 4 bytes, but we still don't get what is happening here; Can anyone explain it?
C's pointer arithmetic always works like this; addition and subtraction is always in terms of the item being pointed at, not in bytes.
Compare it to array indexing: as you might know, the expression a[i] is equivalent to *(a + i), for any pointer a and integer i. Thus, it must be the case that the addition happens in terms of the size of each element of a.
To work around it, cast the structure pointer down to (char *) before the add.
When you add an integer to a pointer, it increments by that many strides (i.e. myPointer + x will increment by x*sizeof(x). If this didn't happen, it would be possible to have unaligned integers, which is many processor architectures is a fault and will cause some funky behaviour, to say the least.
Take the following as an example
char* foo = (char*)0x0; // Foo = 0
foo += 5; // foo = 5
short* bar = (short*)0x0; // Bar = 0; (we assume two byte shorts)
bar += 5; // Bar = 0xA (10)
int* foobar = (int*)0x0; // foobar = 0; (we assume four byte ints)
foobar += 2; // foobar = 8;
char (*myArr)[8]; // A pointer to an array of chars, 8 size
myArr += 2; // myArr = 0x10 (16). This is because sizeof(char[8]) = 8;
Example
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;
/* let us have array address in pointer */
ptr = var;
for ( i = 0; i < MAX; i++)
{
printf("Address of var[%d] = %x\n", i, ptr );
printf("Value of var[%d] = %d\n", i, *ptr );
/* move to the next location */
ptr++;
}
return 0;
}
Output::
Address of var[0] = bfb7fe3c
Value of var[0] = 10
Address of var[1] = bfb7fe40
Value of var[1] = 100
Address of var[2] = bfb7fe44
Value of var[2] = 200
You can deduce from the example that, a pointer increments itself by "Number Of Bytes" = "Size of the type it is pointing to". Here it is, Number Of bytes = sizeof(int). Similarly, it will increment itself 1 byte in case of char.
This is sample code my teacher showed us about "How to dynamically allocate an array in C?". But I don't fully understand this. Here is the code:
int k;
int** test;
printf("Enter a value for k: ");
scanf("%d", &k);
test = (int **)malloc(k * sizeof(int*));
for (i = 0; i < k; i++) {
test[i] = (int*)malloc(k * sizeof(int)); //Initialize all the values
}
I thought in C, to define an array you had to put the [] after the name, so what exactly is int** test; isn't it just a pointer to a pointer? And the malloc() line is also really confusing me.....
According to declaration int** test; , test is pointer to pointer, and the code pice allocating memory for a matrix of int values dynamically using malloc function.
Statement:
test = (int **)malloc(k * sizeof(int*));
// ^^------^^-------
// allocate for k int* values
Allocate continue memory for k pointers to int (int*). So suppose if k = 4 then you gets something like:
temp 343 347 351 355
+----+ +----+----+----+----+
|343 |---►| ? | ? | ? | ? |
+----+ +----+----+----+----+
I am assuming addresses are of four bytes and ? means garbage values.
temp variable assigned returned address by malloc, malloc allocates continues memory blocks of size = k * sizeof(int**) that is in my example = 16 bytes.
In the for loop you allocate memory for k int and assign returned address to temp[i] (location of previously allocated array).
test[i] = (int*)malloc(k * sizeof(int)); //Initialize all the values
// ^^-----^^----------
// allocate for k int values
Note: the expression temp[i] == *(temp + i). So in for loop in each iterations you allocate memory for an array of k int values that looks something like below:
First malloc For loop
--------------- ------------------
temp
+-----+
| 343 |--+
+-----+ |
▼ 201 205 209 213
+--------+ +-----+-----+-----+-----+
343 | |= *(temp + 0) | ? | ? | ? | ? | //for i = 0
|temp[0] |-------| +-----+-----+-----+-----+
| 201 | +-----------▲
+--------+ 502 506 510 514
| | +-----+-----+-----+-----+
347 |temp[1] |= *(temp + 1) | ? | ? | ? | ? | //for i = 1
| 502 |-------| +-----+-----+-----+-----+
+--------+ +-----------▲
| | 43 48 52 56
351 | 43 | +-----+-----+-----+-----+
|temp[2] |= *(temp + 2) | ? | ? | ? | ? | //for i = 2
| |-------| +-----+-----+-----+-----+
+--------+ +-----------▲
355 | |
| 9002 | 9002 9006 9010 9014
|temp[3] | +-----+-----+-----+-----+
| |= *(temp + 3) | ? | ? | ? | ? | //for i = 3
+--------+ | +-----+-----+-----+-----+
+-----------▲
Again ? means garbage values.
Additional points:
1) You are casting returned address by malloc but in C you should avoid it. Read Do I cast the result of malloc? just do as follows:
test = malloc(k* sizeof(int*));
for (i = 0; i < k; i++){
test[i] = malloc(k * sizeof(int));
}
2) If you are allocating memory dynamically, you need to free memory explicitly when your work done with that (after freeing dynamically allocated memory you can't access that memory). Steps to free memory for test will be as follows:
for (i = 0; i < k; i++){
free(test[i]);
}
free(test);
3) This is one way to allocate memory for 2D matrix as array of arrays if you wants to allocate completely continues memory for all arrays check this answer: Allocate memory 2d array in function C
4) If the description helps and you want to learn for 3D allocation Check this answer: Matrix of String or/ 3D char array
Remember that arrays decays to pointers, and can be used as pointers. And that pointers can be used as arrays. In fact, indexing an array can be seen as a form or pointer arithmetics. For example
int a[3] = { 1, 2, 3 }; /* Define and initialize an array */
printf("a[1] = %d\n", a[1]); /* Use array indexing */
printf("*(a + 1) = %d\n", *(a + 1)); /* Use pointer arithmetic */
Both outputs above will print the second (index 1) item in the array.
The same way is true about pointers, they can be used with pointer arithmetic, or used with array indexing.
From the above, you can think of a pointer-to-pointer-to.type as an array-of-arrays-of-type. But that's not the whole truth, as they are stored differently in memory. So you can not pass an array-of-arrays as argument to a function which expects a pointer-to-pointer. You can however, after you initialized it, use a pointer-to-pointer with array indexing like normal pointers.
malloc is used to dynamically allocate memory to the test variable think of the * as an array and ** as an array of arrays but rather than passing by value the pointers are used to reference the memory address of the variable. When malloc is called you are allocating memory to the test variable by getting the size of an integer and multiplying by the number of ints the user supplies, because this is not known before the user enters this.
Yes it is perfectly Ok. test is pointer to pointer and so test[i] which is equivalent to writing test + i will be a pointer. For better understanding please have a look on this c - FAQ.
Yes indeed, int** is a pointer to a pointer. We can also say it is an array of pointers.
test = (int **) malloc(k * sizeof(int*));
This will allocate an array of k pointers first. malloc dynamically allocates memory.
test[i] = (int*) malloc(k * sizeof(int));
This is not necessary as it is enough to
test[i] = (int*) malloc(sizeof(int*));
Here we allocate each of the array places to point to a valid memory. However for base types like int this kind of allocation makes no sense. It is usefull for larger types (structs).
Each pointer can be accessed like an array and vice versa for example following is equivalent.
int a;
test[i] = &a;
(test + i) = &a;
This could be array test in memory that is allocated beginning at offset 0x10000000:
+------------+------------+
| OFFSET | POINTER |
+------------+------------+
| 0x10000000 | 0x20000000 | test[0]
+------------+------------+
| 0x10000004 | 0x30000000 | test[1]
+------------+------------+
| ... | ...
Each element (in this example 0x2000000 and 0x30000000) are pointers to another allocated memory.
+------------+------------+
| OFFSET | VALUE |
+------------+------------+
| 0x20000000 | 0x00000001 | *(test[0]) = 1
+------------+------------+
| ...
+------------+------------+
| 0x30000000 | 0x00000002 | *(test[1]) = 2
+------------+------------+
| ...
Each of the values contains space for sizeof(int) only.
In this example, test[0][0] would be equivalent to *(test[0]), however test[0][1] would not be valid since it would access memory that was not allocted.
For every type T there exists a type “pointer to T”.
Variables can be declared as being pointers to values of various types, by means of the * type declarator. To declare a variable as a pointer, precede its name with an asterisk.
Hence "for every type T" also applies to pointer types there exists multi-indirect pointers like char** or int*** and so on. There exists also "pointer to array" types, but they are less common than "array of pointer" (http://en.wikipedia.org/wiki/C_data_types)
so int** test declares an array of pointers which points to "int arrays"
in the line test = (int **)malloc(k*sizeof(int*)); puts enough memory aside for k amount of (int*)'s
so there are k amount of pointers to, each pointing to...
test[i] = (int*)malloc(k * sizeof(int)); (each pointer points to an array with the size of k amounts of ints)
Summary...
int** test; is made up of k amount of pointers each pointing to k amount of ints.
int** is a pointer to a pointer of int. take a look at "right-left" rule