Q:

C double pointer

struct counter{
    long long counter;
}

struct instruction{
    struct counter *counter
    int repetitions;
    void (*work_fn)(long long *);
};

int ncounter; //number of counters
struct counter *counter; //counter array

int nthreads; //number of threads
int *ninstructions; //number of instructions

struct instruction **instructions; 

How does this actually works ? I am having trouble with ** pointers

A:

A ** is just a pointer to a pointer. So where an instruction* contains the address of an instruction struct, an instruction** contains the address of an instruction* that contains the address of an instruction object.
To access the instruction pointed to by the pointer pointed to by an instruction**, you just use two asterisks instead of one, like (**p).repetitions or something similar.
You can visualize it like this:
instruction*  ----> instruction
instruction** ----> instruction* ----> instruction

Remember, however, that simply declaring struct instruction** instructions; doesn't actually create an instruction struct. It just creates a pointer that holds a garbage value. You'll have to initialize it:
struct instruction inst;
// set members of inst...
*instructions = &inst;

...

(*instructions)->repetitions++; // or whatever

However, it looks like you're using an instruction** to point to an array of instruction*s. To initialize the array, you need a for loop:
instructions = malloc(sizeof(struct instruction*) * num_of_arrays);
for (i = 0; i < num_of_arrays; ++i)
    instructions[i] = malloc(sizeof(struct instruction) * size_of_each_subarray);

And then you can access an element like instructions[i]->datamember.

