Both @Oka and @Allan Wind have written that you've used () where {} is the correct way to define data in an array.
Below uses a different layout for the small amount of data being specified.
It also shows (similar @Allan's answer) how to let the compiler measure the array's dimensions, uses those values as limits to the loops.
The compiler "knows" there are 3 rows (better than erring humans could do.) If a 4th row is added, re-compiling will use the right value without the programmer remembering to change the loop counter from 3 to 4.
This version uses fewer {} in the code, certain that there is only one executable statement properly indented.
This alternative style is more compact and less garish.
As nice as they are, i & j are overused and, therefore, meaningless. Notice the use of r & c that hints at being row & col. Easier to keep things straight when variable names suggest their meaning and use.
Notice how the indentation in the two versions highlights when printf( "\n" ); will be executed. Whitespace is important for readability. Respect it.
#include <stdio.h> // don't include what you don't need
int main() {
int r, c, num[][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
int nRows = sizeof num/sizeof num[0];
int nCols = sizeof num[0]/sizeof num[0][0];
printf( "Version 1:\n" );
for( r = 0; r < nRows; r++ ) {
for( c = 0; c < nCols; c++ )
printf( "%d, ", num[ r ][ c ] );
printf( "\n" );
}
printf( "\nVersion 2:\n" );
for( r = 0; r < nRows; r++ )
for( c = 0; c < nCols; c++ )
printf( "%d, ", num[ r ][ c ] );
printf( "\n" );
return 0;
}
Output
Version 1:
1, 2,
3, 4,
5, 6,
Version 2:
1, 2, 3, 4, 5, 6,
Others will soon comment that the correct definition would be size_t nRows.... One step at a time...