¿Están secuenciados los accesos a arreglos multidimensionales?

8 minutos de lectura

avatar de usuario de user16217248
usuario16217248

Toma lo siguiente:

int a(void) {
    puts("a");
    return 0;
}

int b(void) {
    puts("b");
    return 1;
}

int c(void) {
    puts("c");
    return 2;
}

int d(void) {
    puts("d");
    return 3;
}

¿Tendrán los siguientes comportamientos predecibles?

int arr[4][4][4][4];
arr[a()][b()][c()][d()] = 1;

¿Está garantizado para imprimir en este orden?

a
b
c
d

Soy consciente de que construcciones como las siguientes no son válidas:

int i;
i = i++;

Esto es porque = es un operador no secuenciado, entonces si i o i++ se evalúa primero no está definido. Es un comportamiento indefinido para acceder y modificar un solo objeto antes que otro punto de secuencia.

Dicho de otra manera, es válido lo siguiente:

int i = 0, arr[4][4][4][4];
arr[i++][i++][i++][i++] = 1;

¿O invoca un comportamiento indefinido debido a la modificación no secuencial y al acceso a i?

De acuerdo con el estándar C, ¿hay un punto de secuencia definido entre cada secuencia sucesiva? [] al indexar una matriz multidimensional?

Para ser claros, ninguno de estos ejemplos tiene que ver con la precedencia, la colocación de paréntesis implícitos, el orden en que los operadores operan en sus operandos. La pregunta es sobre la secuencia, el orden en que operandos mismos son evaluados.

  • x[i] Se define como (*((x)+(i)))entonces x[i][j] se convierte (*((x[i])+(j))) que se convierte (*(((*((x)+(i))))+(j))). No hay puntos de secuencia en esa expresión, por lo que no se especifica el orden de evaluación. (Si elimina los paréntesis superfluos, se simplifica a *(*(x+i)+j).)

    –Raymond Chen

    19 de febrero a las 20:11


  • En cuanto a lo que determina el orden en que se llaman las funciones: La norma contiene reglas sobre el orden de evaluación que lo determinan. De manera similar, el estándar contiene reglas que describen dónde se colocan los puntos de secuencia. Están relacionados pero son distintos: uno no determina al otro, el estándar los determina a ambos.

    – Jason C.

    19 de febrero a las 21:25

  • @ user16217248 AFAIK, C no coloca puntos de secuencia entre las evaluaciones de los índices individuales de una matriz. hay una lista aquí.

    – Jason C.

    19 de febrero a las 21:27

¿Tendrán los siguientes comportamientos predecibles?

int arr[4][4][4][4];
arr[a()][b()][c()][d()] = 1;

No.

Si bien la evaluación de los elementos de la matriz se evaluará de izquierda a derecha, ya que uno es un operando del siguiente, no hay garantía de que los índices de la matriz se evalúen de izquierda a derecha.

Para ser más especifico, arr[a()] se evalúa antes arr[a()][b()]que se evalúa antes arr[a()][b()][c()]que se evalúa antes arr[a()][b()][c()][d()]. Sin embargo, a(), b(), c()y d() puede evaluarse en cualquier orden.

Sección 6.5p3 de la estándar C en cuanto a las expresiones dice:

La agrupación de operadores y operandos se indica mediante la sintaxis. Salvo que se especifique más adelante, los efectos secundarios y los cálculos de valor de las subexpresiones no están secuenciados

Las secciones 6.5.2.1 con respecto a los subíndices de matrices no mencionan la secuenciación de los operandos E1[E2]aunque sí afirma que la expresión anterior es exactamente equivalente a (*((E1)+(E2))). Luego, mirando la sección 6.5.3.2 con respecto al operador indirecto * y el apartado 6.5.6 sobre el operador aditivo +, ni hacen ninguna mención de que la evaluación de sus operandos esté secuenciada de ninguna manera. Entonces se aplica 6.5p3, y las funciones a, b, cy d puede llamarse en cualquier orden.

Por las mismas razones, esto:

arr[i++][i++][i++][i++] = 1;

disparadores comportamiento indefinido ya que la evaluación de los índices de la matriz no está secuenciada entre sí y tiene múltiples efectos secundarios en el mismo objeto sin un punto de secuencia.

  • Sin embargo, arr[a()][b()][c()][d()] podría estar bien si a(), b(), c()y d() eran independientes entre sí.

    – Spencer

    20 de febrero a las 14:41


  • @Spencer Está bien de cualquier manera, ya que las llamadas de función no se intercalarán entre sí. Entonces, no hay un comportamiento indefinido en ese caso, solo un comportamiento no especificado.

    – dbush

    20 de febrero a las 14:48

  • Depende de tu significado de OK. Dado que las funciones de OP tienen efectos secundarios comunes, no son realmente “independientes”. Solo creo que agregar el detalle en mi primer comentario evitaría la implicación de un categórico “no hagas esto”.

    – Spencer

    20 de febrero a las 14:53


  • @Spencer Son dependientes porque modifican el mismo objeto, específicamente el indicador de posición de flujo de stdouten orden no especificado.

    – usuario16217248

    10 de marzo a las 18:11

avatar de usuario de user16217248
usuario16217248

Los índices de acceso a un arreglo multidimensional son no garantizado para evaluar en cualquier orden particular. una demostración muestra las funciones que se llaman de izquierda a derecha, pero seleccionar un compilador diferente en Godbolt las evalúa de derecha a izquierda.

Tras una mayor investigación, el segundo ejemplo de código provoca una advertencia con Clang:

warning: multiple unsequenced modifications
      to 'i' [-Wunsequenced]
        arr[i++][i++][i++][i++] = 1;
             ^    ~~

El acceso a la matriz multidimensional se puede dividir en una serie de (array)[index] dónde array es una dimensión superior de la matriz multidimensional (arr en sí mismo es la dimensión más alta) y index es la expresión de índice, como una llamada de función o i++ expresión.

La Norma sostiene que lhs[rhs] es equivalente a *((lhs)+(rhs))por lo tanto, es indeterminado si cualquier dado indexo el array está indexando, se evalúa primero, ya que el + El operador no está secuenciado. En todos los casos en que array no es arr mismo, evaluando array implica evaluar es index en una dimensión aún más alta.

Por lo tanto, el orden en que se evalúan los índices de acceso a un arreglo multidimensional es indeterminado.

  • @Emile El estándar C no hace distinción, que yo sepa, entre un comportamiento de tiempo de compilación impredecible pero consistente en el tiempo de ejecución o un período de comportamiento impredecible. Mientras que en la práctica, algunas construcciones, si bien tienen un comportamiento no especificado, se comportarán de la misma manera entre ejecuciones una vez compiladas, en lo que respecta al estándar C, el comportamiento no especificado es un comportamiento no especificado. La cláusula es del tipo de conducta a la que se prevén dos o más posibilidades y no se imponen más requisitos sobre los que se opta en cada momento.

    – usuario16217248

    21 de febrero a las 16:59


  • @user16217248: tenga en cuenta que el estándar reconoce tres situaciones en las que caracteriza a un programa como invocando un comportamiento indefinido (un programa ejecuta una construcción no portátil que podría ser correcta, un programa ejecuta una construcción errónea o un programa correcto y portátil recibe datos erróneos) , y aunque la Norma renuncia a la jurisdicción en las tres situaciones, eso no pretende invitar a los demonios nasales en las tres.

    – Super gato

    21 de febrero a las 21:16

  • @user16217248: intento de usar el modo binario para acceder a un archivo que se creó en modo de texto, o viceversa. No existe ningún medio portátil a través del cual un programa pueda determinar si un archivo en particular se creó en modo binario o en modo de texto, y en los sistemas operativos que tienen diferentes modos de archivo, pueden ocurrir cosas extrañas si un archivo se escribe con un modo y se lee con el otro. . Algunos sistemas, por ejemplo, pueden preceder a cada línea de un archivo de texto con un par de bytes que indican la longitud, y pueden comportarse de forma extraña si el número de bytes que preceden al texto de la última línea, más su longitud,…

    – Super gato

    24 de febrero a las 21:57

  • @user16217248: Sí. Por supuesto, en muchas plataformas no sucedería nada extraño, y en la mayoría de las demás, el intento de abrir el archivo simplemente fallaría, pero el estándar no impone requisitos sobre lo que sucede porque era obvio que las implementaciones deberían procesar tales acciones “de forma documentada”. forma característica del entorno” cuando se dirige a entornos que tenían un comportamiento característico documentado. La noción de que el Estándar solo caracterizó como UB las acciones que tenían la intención de invitar a los demonios nasales es una mentira utilizada para justificar la estupidez de los compiladores.

    – Super gato

    24 de febrero a las 23:18


  • @user16217248: The Standard no intenta anticipar ni prohibir todas las formas en que un escritor de compilador obtuso podría romper programas que otros compiladores procesarían de manera significativa, porque los autores reconocieron que nadie que quisiera vender compiladores se involucraría en un comportamiento tan destructivo. Desafortunadamente, no pudieron anticipar que un compilador que estaba exento de las fuerzas del mercado podría lograr el dominio.

    – Super gato

    24 de febrero a las 23:26

Dadas las construcciones

int x; // At file scope
... and then within some function
arr[f()][x] += 1;
arr[x][f()] += 2;

no sería raro que un compilador procesara cada línea realizando la llamada a f() antes de la lectura de x, independientemente de si la llamada a la función fue el primer o el segundo índice. Si bien la adición real del índice interno al puntero del subconjunto puede no ser posible hasta después de calcular la dirección del subconjunto, un compilador puede calcular cualquiera o todos los subíndices del conjunto antes de comenzar a trabajar en los cálculos de dirección.

¿Ha sido útil esta solución?