ventajas usa preservativos preservativo precios marcas historia efectividad desventajas definicion como colocar campo c++ c arrays struct

c++ - usa - preservativos marcas



En una estructura, ¿es legal usar un campo de matriz para acceder a otro? (9)

Como ejemplo, considere la siguiente estructura:

struct S { int a[4]; int b[4]; } s;

¿Sería legal escribir sa[6] y esperar que sea igual a sb[2] ? Personalmente, creo que debe ser UB en C ++, mientras que no estoy seguro acerca de C. Sin embargo, no pude encontrar nada relevante en los estándares de los lenguajes C y C ++.

Actualizar

Hay varias respuestas que sugieren formas de asegurarse de que no haya relleno entre los campos para que el código funcione de manera confiable. Me gustaría enfatizar que si dicho código es UB, entonces la ausencia de relleno no es suficiente. Si es UB, entonces el compilador es libre de asumir que los accesos a Sa[i] y Sb[j] no se superponen y el compilador es libre de reordenar dichos accesos de memoria. Por ejemplo,

int x = s.b[2]; s.a[6] = 2; return x;

puede ser transformado a

s.a[6] = 2; int x = s.b[2]; return x;

que siempre devuelve 2 .


¿Sería legal escribir sa [6] y esperar que sea igual a sb [2]?

No Porque acceder a una matriz fuera del comportamiento indefinido invocado vinculado en C y C ++.

C11 J.2 Comportamiento indefinido

  • La adición o sustracción de un puntero en, o más allá, un objeto de matriz y un tipo entero produce un resultado que apunta más allá del objeto de matriz y se utiliza como el operando de un operador unario * que se evalúa (6.5.6).

  • Un subíndice de matriz está fuera de rango, incluso si un objeto es aparentemente accesible con el subíndice dado (como en la expresión lvalue a[1][7] dada la declaración int a[4][5]) (6.5.6).

El draft estándar C ++ sección 5.7 Operadores aditivos, párrafo 5 dice:

Cuando una expresión que tiene un tipo integral se agrega o resta de un puntero, el resultado tiene el tipo del operando del puntero. Si el operando del puntero apunta a un elemento de un objeto de matriz, y la matriz es lo suficientemente grande, el resultado apunta a un elemento desplazado del elemento original de tal manera que la diferencia de los subíndices de los elementos de matriz originales y resultantes es igual a la expresión integral. [...] Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto de matriz, o uno pasado el último elemento del objeto de matriz, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento es indefinido.


Además de la respuesta de @rsp ( Undefined behavior for an array subscript that is out of range ), puedo agregar que no es legal acceder a b través de a porque el lenguaje C no especifica cuánto espacio de relleno puede haber entre el final de área asignada para ay el inicio de b, por lo que incluso si puede ejecutarlo en una implementación particular, no es portátil.

instance of struct: +-----------+----------------+-----------+---------------+ | array a | maybe padding | array b | maybe padding | +-----------+----------------+-----------+---------------+

El segundo relleno puede fallar, así como la alineación del struct object es la alineación de a que es la misma que la alineación de b pero el lenguaje C tampoco impone que el segundo relleno no esté allí.


Como @MartinJames mencionó en un comentario, si necesita garantizar que b están en memoria contigua (o al menos pueden ser tratados como tales, (editar) a menos que su arquitectura / compilador use un tamaño / desplazamiento de bloque de memoria inusual y forzado alineación que requeriría agregar relleno), debe usar una union .

union overlap { char all[8]; /* all the bytes in sequence */ struct { /* (anonymous struct so its members can be accessed directly) */ char a[4]; /* padding may be added after this if the alignment is not a sub-factor of 4 */ char b[4]; }; };

No puede acceder directamente a b desde a (por ejemplo, a[6] , como solicitó), pero puede acceder a los elementos de a y b utilizando all (por ejemplo, all[6] refieren a la misma ubicación de memoria que b[2] ).

(Editar: podría reemplazar 8 y 4 en el código anterior con 2*sizeof(int) y sizeof(int) , respectivamente, para que sea más probable que coincida con la alineación de la arquitectura, especialmente si el código necesita ser más portátil, pero luego debe tener cuidado para evitar hacer suposiciones acerca de cuántos bytes hay en a , b o all , sin embargo, esto funcionará en lo que probablemente sean las alineaciones de memoria más comunes (1, 2 y 4 bytes). )

Aquí hay un ejemplo simple:

#include <stdio.h> union overlap { char all[2*sizeof(int)]; /* all the bytes in sequence */ struct { /* anonymous struct so its members can be accessed directly */ char a[sizeof(int)]; /* low word */ char b[sizeof(int)]; /* high word */ }; }; int main() { union overlap testing; testing.a[0] = ''a''; testing.a[1] = ''b''; testing.a[2] = ''c''; testing.a[3] = ''/0''; /* null terminator */ testing.b[0] = ''e''; testing.b[1] = ''f''; testing.b[2] = ''g''; testing.b[3] = ''/0''; /* null terminator */ printf("a=%s/n",testing.a); /* output: a=abc */ printf("b=%s/n",testing.b); /* output: b=efg */ printf("all=%s/n",testing.all); /* output: all=abc */ testing.a[3] = ''d''; /* makes printf keep reading past the end of a */ printf("a=%s/n",testing.a); /* output: a=abcdefg */ printf("b=%s/n",testing.b); /* output: b=efg */ printf("all=%s/n",testing.all); /* output: all=abcdefg */ return 0; }


El Estándar no impone restricciones sobre lo que deben hacer las implementaciones cuando un programa intenta usar un subíndice de matriz fuera de los límites en un campo de estructura para acceder a un miembro de otro. Los accesos fuera de límites son, por lo tanto, "ilegales" en programas estrictamente conformes , y los programas que hacen uso de tales accesos no pueden ser 100% portátiles y libres de errores simultáneamente. Por otro lado, muchas implementaciones definen el comportamiento de dicho código, y los programas que están dirigidos únicamente a tales implementaciones pueden explotar dicho comportamiento.

Hay tres problemas con dicho código:

  1. Si bien muchas implementaciones presentan estructuras de manera predecible, el Estándar permite que las implementaciones agreguen relleno arbitrario antes que cualquier miembro de la estructura que no sea el primero. El código podría usar sizeof o offsetof para garantizar que los miembros de la estructura se coloquen como se esperaba, pero los otros dos problemas permanecerían.

  2. Dado algo como:

    if (structPtr->array1[x]) structPtr->array2[y]++; return structPtr->array1[x];

    normalmente sería útil para un compilador suponer que el uso de structPtr->array1[x] producirá el mismo valor que el uso anterior en la condición "if", aunque cambiaría el comportamiento del código que depende del aliasing entre las dos matrices.

  3. Si array1[] tiene, por ejemplo, 4 elementos, un compilador tiene algo como:

    if (x < 4) foo(x); structPtr->array1[x]=1;

podría concluir que dado que no habría casos definidos donde x no sea menor que 4, podría llamar a foo(x) incondicionalmente.

Desafortunadamente, si bien los programas pueden usar sizeof o offsetof para garantizar que no haya sorpresas con el diseño de estructura, no hay forma de que puedan probar si los compiladores prometen abstenerse de las optimizaciones de los tipos # 2 o # 3. Además, el Estándar es un poco vago sobre lo que se entendería en un caso como:

struct foo {char array1[4],array2[4]; }; int test(struct foo *p, int i, int x, int y, int z) { if (p->array2[x]) { ((char*)p)[x]++; ((char*)(p->array1))[y]++; p->array1[z]++; } return p->array2[x]; }

El estándar es bastante claro que el comportamiento solo se definiría si z está en el rango 0..3, pero dado que el tipo de matriz p-> en esa expresión es char * (debido a la descomposición) no está claro el reparto en el acceso usar y tendría algún efecto. Por otro lado, dado que convertir el puntero al primer elemento de una estructura en char* debería producir el mismo resultado que convertir un puntero de estructura a char* , y el puntero de estructura convertido debería ser utilizable para acceder a todos los bytes, parecería que el el acceso usando x debe definirse para (como mínimo) x = 0..7 [si el desplazamiento de array2 es mayor que 4, afectaría el valor de x necesario para golpear a los miembros de array2 , pero algún valor de x podría hacerlo con comportamiento definido].

En mi humilde opinión, un buen remedio sería definir el operador de subíndice en los tipos de matriz de una manera que no implique la caída del puntero. En ese caso, las expresiones p->array[x] y &(p->array1[x]) podrían invitar a un compilador a asumir que x es 0..3, pero p->array+x y *(p->array+x) requeriría un compilador para permitir la posibilidad de otros valores. No sé si algún compilador hace eso, pero el Estándar no lo requiere.


Es legal? No. Como otros mencionaron, invoca Comportamiento indefinido .

¿Funcionará? Eso depende de tu compilador. Eso es lo que ocurre con el comportamiento indefinido: es indefinido .

En muchos compiladores de C y C ++, la estructura se presentará de tal manera que b seguirá inmediatamente a a en la memoria y no habrá comprobación de límites. Por lo tanto, acceder a a [6] será efectivamente igual a b [2] y no causará ningún tipo de excepción.

Dado

struct S { int a[4]; int b[4]; } s

y suponiendo que no haya relleno adicional , la estructura es realmente una forma de ver un bloque de memoria que contiene 8 enteros. Puede convertirlo a (int*) y ((int*)s)[6] apuntaría a la misma memoria que sb[2] .

¿Deberías confiar en este tipo de comportamiento? Absolutamente no. Indefinido significa que el compilador no tiene que soportar esto. El compilador es libre de rellenar la estructura, lo que podría suponer que & (sb [2]) == & (sa [6]) es incorrecto. El compilador también podría agregar verificación de límites en el acceso a la matriz (aunque habilitar las optimizaciones del compilador probablemente deshabilitaría dicha verificación).

He experimentado los efectos de esto en el pasado. Es bastante común tener una estructura como esta

struct Bob { char name[16]; char whatever[64]; } bob; strcpy(bob.name, "some name longer than 16 characters");

Ahora bob.whatever será "de 16 caracteres". (por eso siempre debes usar strncpy, por cierto)


La respuesta de Jed Schaff está en el camino correcto, pero no del todo correcta. Si el compilador inserta relleno entre a y b , su solución seguirá fallando. Sin embargo, si declaras:

typedef struct { int a[4]; int b[4]; } s_t; typedef union { char bytes[sizeof(s_t)]; s_t s; } u_t;

Ahora puede acceder (int*)(bytes + offsetof(s_t, b)) para obtener la dirección de sb , sin importar cómo el compilador presenta la estructura. La macro offsetof() se declara en <stddef.h> .

La expresión sizeof(s_t) es una expresión constante, legal en una declaración de matriz en C y C ++. No dará una matriz de longitud variable. (Disculpas por haber leído mal el estándar C antes. Pensé que eso sonaba mal).

Sin embargo, en el mundo real, dos matrices consecutivas de int en una estructura se presentarán de la manera que espera. (Es posible que pueda diseñar un contraejemplo muy ingenioso estableciendo el límite de a en 3 o 5 en lugar de 4 y luego haciendo que el compilador alinee tanto a como b en un límite de 16 bytes). En lugar de métodos complicados para intentar obtener un programa que no haga suposiciones más allá de la estricta redacción del estándar, desea algún tipo de codificación defensiva, como static assert(&both_arrays[4] == &s.b[0], ""); . Estos no agregan gastos generales de tiempo de ejecución y fallarán si su compilador está haciendo algo que rompería su programa, siempre que no active UB en la afirmación misma.

Si desea una forma portátil de garantizar que ambas sub-matrices estén empaquetadas en un rango de memoria contiguo, o dividir un bloque de memoria de la otra manera, puede copiarlas con memcpy() .


Respuesta corta: No. Estás en la tierra del comportamiento indefinido.

Respuesta larga: No. Pero eso no significa que no pueda acceder a los datos de otras formas incompletas ... si está utilizando GCC, puede hacer algo como lo siguiente (elaboración de la respuesta de dwillis):

struct __attribute__((packed,aligned(4))) Bad_Access { int arr1[3]; int arr2[3]; };

y luego puedes acceder a través de ( fuente Godbolt + asm ):

int x = ((int*)ba_pointer)[4];

Pero ese yeso viola el alias estricto, por lo que solo es seguro con g++ -fno-strict-aliasing . Puede lanzar un puntero de estructura a un puntero al primer miembro, pero luego está de vuelta en el barco UB porque está accediendo fuera del primer miembro.

Alternativamente, simplemente no hagas eso. Ahorre a un futuro programador (probablemente usted mismo) la angustia de ese desastre.

Además, mientras estamos en eso, ¿por qué no usar std :: vector? No es infalible, pero en el back-end tiene guardias para evitar ese mal comportamiento.

Apéndice:

Si realmente te preocupa el rendimiento:

Digamos que tiene dos punteros del mismo tipo al que está accediendo. Es muy probable que el compilador suponga que ambos punteros tienen la posibilidad de interferir, y creará una lógica adicional para protegerlo de hacer algo tonto.

Si jura solemnemente al compilador que no está tratando de alias, el compilador lo recompensará generosamente: ¿la palabra clave restringida proporciona beneficios significativos en gcc / g ++

Conclusión: no seas malvado; tu futuro yo, y el compilador te lo agradecerá.


b son dos matrices diferentes, y a se define como que contiene 4 elementos. Por lo tanto, a[6] accede a la matriz fuera de los límites y, por lo tanto, es un comportamiento indefinido. Tenga en cuenta que el subíndice de matriz a[6] se define como *(a+6) , por lo que la prueba de UB en realidad está dada por la sección "Operadores aditivos" junto con punteros ". Consulte la siguiente sección del estándar C11 (por ejemplo, this versión borrador en línea) que describe este aspecto:

6.5.6 Operadores aditivos

Cuando una expresión que tiene un tipo entero se agrega o resta de un puntero, el resultado tiene el tipo del operando del puntero. Si el operando del puntero apunta a un elemento de un objeto de matriz, y la matriz es lo suficientemente grande, el resultado apunta a un elemento desplazado del elemento original de modo que la diferencia de los subíndices de los elementos de matriz originales y resultantes es igual a la expresión entera. En otras palabras, si la expresión P apunta al elemento i-ésimo de un objeto de matriz, las expresiones (P) + N (equivalentemente, N + (P)) y (P) -N (donde N tiene el valor n) apuntan a, respectivamente, los elementos i + n-th e in-th del objeto de matriz, siempre que existan. Además, si la expresión P apunta al último elemento de un objeto de matriz, la expresión (P) +1 señala uno más allá del último elemento del objeto de matriz, y si la expresión Q apunta uno más allá del último elemento de un objeto de matriz, la expresión (Q) -1 apunta al último elemento del objeto de matriz. Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto de matriz, o uno pasado el último elemento del objeto de matriz, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento es indefinido . Si el resultado apunta uno más allá del último elemento del objeto de matriz, no se utilizará como el operando de un operador unario * que se evalúa.

El mismo argumento se aplica a C ++ (aunque no se cita aquí).

Además, aunque es un comportamiento claramente indefinido debido al hecho de exceder los límites de la matriz de a , tenga en cuenta que el compilador podría introducir relleno entre los miembros b , de modo que, incluso si se permitieran tales aritméticos de puntero, a+6 no necesariamente produciría la misma dirección que b+2 .


No , dado que acceder a una matriz fuera de los límites invoca Comportamiento indefinido , tanto en C como en C ++.