Except when it is the operand of the sizeof or unary & operators, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array.
Assuming the declaration
char ma[5][30];
then all of the following are true:
The expression ma has type "5-element array of 30-element array of char". Unless ma is an operand to either sizeof or unary &, it will be converted to an expression of type "pointer to 30-element array of char", or char (*)[30], and its value will be the address of the first element in the array, or &ma[0].
The expression ma[i] has type "30-element array of char". Unless ma[i] is an operand to either sizeof or unary &, it will be converted to an expression of type "pointer to char", or char *, and its value will be the address of the first element in the array, or &ma[i][0].
The expression ma[i][j] has type char.
The expression &ma has type "pointer to 5-element array of 30-element array of char, or char (*)[5][30].
The expression &ma[i] has type char (*)[30].
The expression &ma[i][j] has type char *.
The values of the expressions ma, &ma, ma[0], &ma[0], and &ma[0][0] are all the same; the address of the array is the same as the address of the first element of the array.
Note that the types char (*)[30] and char (*)[5][30] are not compatible with char *, or with each other; if you want to assign a value of those types to a variable of type char *, you will need to use an explicit cast, such as char *p = (char *) ma;.
Edit
Types matter; pointer arithmetic is based on the pointed-to type, so an expression like ptr+1 will give different results based on the type that ptr points to. For example:
#include <stdio.h>
int main( void )
{
char ma[5][30] = {{0}};
char (*p0)[5][30] = &ma;
char (*p1)[30] = &ma[0];
char *p2 = &ma[0][0];
printf("%5s%-15s%-15s\n"," ","ptr","ptr+1");
printf("%5s%-15s%-15s\n"," ","-----","-----");
printf("%-5s%-15p%-15p\n","p0", (void *) p0, (void *) (p0+1));
printf("%-5s%-15p%-15p\n","p1", (void *) p1, (void *) (p1+1));
printf("%-5s%-15p%-15p\n","p2", (void *) p2, (void *) (p2+1));
return 0;
}
I create three pointers of different types; p0 is of type char (*)[5][30] and takes the result of &ma, p1 is of type char (*)[30] and takes the result of &ma[0], and p2 is of type char * and takes the result of &ma[0][0]. I then print the value of each pointer, than the value of each pointer plus 1. Here are the results:
ptr ptr+1
----- -----
p0 0x7fff36670d30 0x7fff36670dc6
p1 0x7fff36670d30 0x7fff36670d4e
p2 0x7fff36670d30 0x7fff36670d31
Each pointer starts out with the same value, but adding 1 to the pointer gives different results based on the type. p0 points to a 5x30-element array of char, so p0 + 1 will point to the beginning of the next 5x30-element array of char. p1 points to a 30-element array of char, so p1 + 1 points to the next 30-element array of char (ma[1]). Finally, p2 points to a single char, so p2 + 1 points to the next char (ma[0][1]).