I have been taught that v is a pointer to the first element in the v array.
You have been taught incorrectly. v is not a pointer - no space for a pointer is materialized as part of the array. What you get is something like this:
+---+
v: | 1 | v[0]
+---+
| 2 | v[1]
+---+
| 3 | v[2]
+---+
| 4 | v[3]
+---+
| 5 | v[4]
+---+
and not this:
+---+
v: | |
+---+
|
|
V
+---+
| 1 | v[0]
+---+
| 2 | v[1]
+---+
| 3 | v[2]
+---+
| 4 | v[3]
+---+
| 5 | v[4]
+---+
Except when it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character 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.
When you write something like foo( v ), or printf( "%p\n", (void *) v), or even just v[i], the expression v is converted from type "5-element array of int" to "pointer to int", and the value of the expression is the same as &v[0].
However, when you write sizeof v, that conversion doesn't happen - sizeof evaluates to the number of bytes in the entire array (5 * sizeof (int)). Similarly, the type of the expression &v is int (*)[5] (pointer to 5-element array of int), not int **.
This is why sizeof v yields 20, while sizeof (v + 0) yields 4 - in the second case, v is not the operand of sizeof, the expression (v + 0) is the operand of sizeof. In the expression (v + 0), v decays to type int *. Note that you will get a different result if you write sizeof v + 0 - sizeof has higher precedence than the addition operator +, so that expression would be parsed as (sizeof v) + 0.