@paddy has a nice answer; only adding a bit (as of my toughs by your reply on my comment - was a bit late to the game). This rely on pow() , (and log10 for some nicety in print), tho so; if using gcc compile with -lm:
basemight be a bit confusing here - but guess you get the meaning.
gcc -Wall -Wextra -pedantic -o combo combo.c -lm
/* gcc - Wall -Wextra -pedantic  -o combo combo.c -lm */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
static void prnt_combo(unsigned number, unsigned bits, int base)
{
    if (!bits)
        return;
    prnt_combo(number / base, --bits, base);
    printf("%d", number % base);
}
void prnt_combos(int bits, int base)
{
    int i;
    int n = pow(base, bits);
    int wp = log10(n) + 1;
    fprintf(stderr,
        "Printing all combinations of 0 to %d by width of %d numbers. "
        "Total %d.\n",
        base - 1, bits, n
    );
    for (i = 0; i < n; i++) {
        fprintf(stderr, "%*d : ", wp, i);
        prnt_combo(i, bits, base);
        printf("\n");
    }
}
/* Usage: ./combo [<bits> [<base>]]
 *        Defaults to ./combo 3 2
 * */
int main(int argc, char *argv[])
{
    int bits = argc > 1 ? strtol(argv[1], NULL, 10) : 3;
    int base = argc > 2 ? strtol(argv[2], NULL, 10) : 2;
    prnt_combos(bits, base);
    return 0;
}
Sample:
$ ./combo 4 2
Printing all combinations of 0 to 1 by width of 4 numbers. Total 16.
 0 : 0000
 1 : 0001
 2 : 0010
 3 : 0011
 4 : 0100
 5 : 0101
 6 : 0110
 7 : 0111
 8 : 1000
 9 : 1001
10 : 1010
11 : 1011
12 : 1100
13 : 1101
14 : 1110
15 : 1111
Or clean output:
$ ./combo 3 2 >&2-
000
001
010
011
100
101
110
111
You might like to add something like:
if (base > 10)
    printf("%x", number % base);
else
    printf("%d", number % base);
in prnt_combo(). This way you get i.e. by 2 16:
    0 : 00
    1 : 01
    2 : 02
    3 : 03
    4 : 04
  ...
  250 : fa
  251 : fb
  252 : fc
  253 : fd
  254 : fe
  255 : ff