Para entender eso debes tener muy claro qué es una variable.
Una variable es una zona de memoria que alguien te ha dejado para que pongas cosas dentro. ¿Puedes cambiar esa zona de memoria? No, pero puedes cambiar lo que hay dentro.
Para verlo veamos un escalar, un int (4 bytes). Supongamos que el sistema te da la dirección 100 para tu variable, podrías hacer esto:
100: 0x00
101: 0x01
102: 0xFF
103: 0x00
Cómo puedes intuir el cambiar la dirección de los datos es absurdo, la memoria es la que es, es algo físico. Si acaso puedes copiar el contenido en otro sitio pero tienes que dar otro nombre a ese otro sitio para referenciarte a él.
Vayamos ahora a por los punteros, esas variables que contienen direcciones de memoria. Al igual que los escalares son variables y esto significa que están ancladas en una dirección de memoria. De la misma forma contienen números pero estos representan otras direcciones de memoria, y por contener direcciones de memoria tienen una aritmética especial; por lo demás son como escalares.
Las variables (escalares y punteros) devuelven su contenido, es decir: dónde C/C++ vea su nombre lo traducirá por su contenido.
Los arrays son una bestia extraña, una quimera. Un array es una ristra de elementos iguales contiguos. Supongamos un array de cuatro int y que éste empieza en la dirección de memoria 100:
100: 0x123847
104: 0x1038
108: 0x77483
112: 0x0
Cómo ves, al igual que los escalares cada elemento ocupa una dirección física que no se puede cambiar. Por tanto a un array no se le puede cambiar de dirección.
Pero a diferencia de los escalares están compuestos de muchos objetos, no sólo uno así que ¿cómo haces referencia a ellos? Para eso podrías hacer que el nombre del array devolviera su dirección de inicio y aplicar aritmética de punteros para referenciar a cada elemento. Así si tu array se llama miArray tendrías que:
miArray: devuelve 100 (igual que miArray+0)
miArray+1: devuelve 104 (recordar que un tipo entero son 4 bytes, aquí 100, 101, 102 y 103)
miArray+2: devuelve 108
miArray+3: devuelve 112
Y ya que tienen aritmética de punteros se dereferencian direcciones de memoria cómo los punteros:
*(miArray+2): devuelve 0x77483
En notación de array:
miArray[2]: devuelve 0x77483
Y esta es la razón por la que los índices de array empiezan por 0. Por otra parte y muy a tener en cuenta: el nombre del array devuelve su dirección de inicio.
Respondiendo a tu pregunta:
Ya has visto que los arrays devuelven su dirección por tanto cuándo pasas por parámetro un array lo que haces realmente es pasar su dirección así que no hay copia por valor de todo el array.
Pero la cosa se complica. ¿Qué ocurre cuándo pasas un array? Verás que el argumento del receptor debe dimensionar todas las dimensiones menos la primera. Algo así:
void miFuncion(int cubos[][100][100][100]);
El array recibe una colección de cubos.
¿Porqué la primera dimensión no está definida? Pues porque puede que tengas una colección de 1 de 100 o de 1000 cubos.
¿Y porqué las otras dimensiones sí están especificadas? C/C++ debe saber cuánto tiene que moverse por memoria cuándo des un índice. Por ejemplo:
La diferencia de memoria entre cubos[0] y cubos[1] es (1-0)*sizeof(int)*100*100*100.
La diferencia de memoria entre cubos[0][0][3][0] y cubos[0][0][5][0] es (5-3)*sizeof(int)*100.