2011-01-07 5 views
13

En C++, la mayoría de las optimizaciones se derivan de la regla de si. Es decir, siempre que el programa se comporte como si no hubiera tenido lugar una optimización, entonces son válidos.Optimización de miembros de datos vacíos: ¿sería posible?

La optimización de base vacía es uno de estos trucos: en algunas condiciones, si la clase base está vacía (no tiene ningún miembro de datos no estáticos), el compilador puede eludir su representación de memoria.

Aparentemente parece que la norma prohíbe esta optimización de los miembros de datos, es decir, incluso si un miembro de datos está vacía, todavía debe tomar por lo menos un valor de byte del lugar: desde N3225, [clase]

4 - Los objetos completos y los subobjetos miembros de clase tienen un tamaño distinto de cero.

Nota: esto conduce a la utilización de la herencia privada para el diseño de políticas con el fin de tener EBO entran en juego cuando sea apropiado

Me preguntaba si, utilizando el como-si la regla, se podría seguir siendo capaz de realizar esta optimización


edición: tras una serie de respuestas y comentarios, y para hacerlo más claro lo que estoy preguntando acerca.

En primer lugar, te voy a dar un ejemplo:

struct Empty {}; 

struct Foo { Empty e; int i; }; 

Mi pregunta es, ¿por qué es sizeof(Foo) != sizeof(int)? En particular, a menos que especifique algún empaque, es probable que debido a problemas de alineación, Foo tenga el doble del tamaño de int, lo que parece ridículamente inflado.

Nota: mi pregunta no es por qué es sizeof(Foo) != 0, esto no es realmente necesario, ya sea por EBO

Según C++, es porque no tiene ningún problema-objeto puede tener un tamaño cero. Sin embargo, una base está autorizada a tener un tamaño cero (EBO), por lo tanto:

struct Bar: Empty { int i; }; 

es probable que (gracias a EBO) a obedecer sizeof(Bar) == sizeof(int).

Steve Jessop parece ser una opinión de que no hay dos subobjetos que tengan la misma dirección. Pensaba en ello, sin embargo, no impide en realidad la optimización en la mayoría de los casos:

Si tiene memoria "sin usar", entonces es trivial:

struct UnusedPadding { Empty e; Empty f; double d; int i; }; 
// chances are that the layout will leave some memory after int 

Pero, de hecho, es incluso "peor "que eso, porque el espacio Empty nunca se escribe (será mejor que no lo haga si EBO entra en acción ...) Y, por tanto, en realidad se podría colocarlo en un lugar ocupado que no es la dirección de otro objeto:

struct Virtual { virtual ~Virtual() {} Empty e; Empty f; int i; }; 
// most compilers will reserve some space for a virtual pointer! 

O, incluso en nuestro caso original:

struct Foo { Empty e; int i; }; // deja vu! 

Uno podría tener (char*)foo.e == (char*)foo.i + 1 si todo lo que quería una dirección diferente.

+1

Eche un vistazo a la biblioteca de Boost [Compressed Pair] (http://www.boost.org/doc/libs/1_45_0/libs/utility/compressed_pair.htm) para ver cómo obtener esta optimización. – GManNickG

+0

@GMan: hábilmente usan EBO. Pero en realidad este uso de EBO es exactamente lo que me impulsó a comenzar mi pregunta. –

+0

Vea esto: [Cuando los programadores usan la Optimización de Base Vacía (EBO)] (http://stackoverflow.com/questions/4325144/scenario-when-do-programmers-use-empty-base-optimization-ebo) – Nawaz

Respuesta

6

Bajo la regla como si-:

struct A { 
    EmptyThing x; 
    int y; 
}; 

A a; 
assert((void*)&(a.x) != (void*)&(a.y)); 

la aserción no debe ser activado. Así que no veo ningún beneficio en hacer que x secretamente tenga el tamaño 0, cuando de todos modos necesitarías agregar relleno a la estructura.

Supongo que, en teoría, un compilador podría rastrear si los identificadores podrían tomarse para los miembros, y realizar la optimización solo si definitivamente no lo son. Esto tendría un uso limitado, ya que habría dos versiones diferentes de la estructura con diferentes diseños: uno para el caso optimizado y otro para el código general.

Pero, por ejemplo, si crea una instancia de A en la pila y hace algo con ella totalmente en línea (o visible para el optimizador), sí, partes de la estructura podrían omitirse por completo. Sin embargo, esto no es específico de los objetos vacíos: un objeto vacío es solo un caso especial de un objeto cuyo almacenamiento no se tiene acceso, y por lo tanto, en algunas situaciones, nunca se podría asignar en absoluto.

+0

sí, esta identidad de dirección es lo que realmente me molesta aquí. Aunque puedo adivinar que podría haber sido útil, un objeto cuya dirección no se toma equivale a un objeto que no se usa (ya que invocar cualquier método requiere su dirección) y no veo cómo un compilador de C++ puede realizar un objeto. sin usar aparte de todas las clases alineadas. ¿Sabes por qué se usa este no-cero? (es decir, si es un remanente de C o todavía se ha utilizado en un estilo más moderno) –

+0

@ Matthieu: bueno, supongo que el tamaño distinto de cero de los objetos completos es para que pueda hacer 'sizeof (array)/sizeof (* array)' y no dividir por cero. No puedo pensar en una razón sólida para el tamaño no-cero de los subobjetos de miembros: si necesita algunos objetos miembro con diferentes direcciones, usted (el programador) podría simplemente asegurarse de que son de tipo 'char' en lugar de' EmptyThing' . Supongo que hay algún lugar en el modelo de objeto/memoria que necesitaría manejo de casos especiales, y es más simple suponer que los subobjetos de miembros son distintos y no se superponen. –

+0

He actualizado algo mi pregunta. No estoy requiriendo un tamaño no nulo de objetos completos (aunque eso también podría ser bueno, permitiendo un montón de otras optimizaciones, y 'sizeof' también es código de compilación, así que no te preocupes por una excepción de divide por cero en tiempo de ejecución) . No entiendo por qué si podemos tener EBO (que es una clase base de tamaño cero) no podemos tener EDMO (que es un miembro de datos de tamaño cero). –

2

C++ por razones técnicas establece que las clases vacías deben tener un tamaño distinto de cero.
Esto es para hacer cumplir que los objetos distintos tienen direcciones de memoria distintas. Por lo tanto, los compiladores insertan silenciosamente un byte en objetos "vacíos".
Esta restricción no se aplica a las partes de la clase base de las clases derivadas, ya que no son autónomas.

-1

Dado struct Empty { }; considere qué ocurre si sizeof(Empty) == 0. El código genérico que asigna el montón para los objetos vacíos podría comportarse fácilmente de manera diferente, como, por ejemplo, un realloc(p, n * sizeof(T)), donde T es , es equivalente a free(p). Si sizeof(Empty) != 0 cosas como memset/memcpy etc. intentarían funcionar en regiones de memoria que no estaban en uso por los objetos Empty. Entonces, el compilador debería coser cosas como sizeof(Empty) sobre la base del uso eventual del valor, eso me suena casi imposible.

Por separado, bajo las reglas actuales de C++, la seguridad de que cada miembro tiene una dirección distinta significa que puede usar esas direcciones para codificar algún estado sobre esos campos, p. Ej. un nombre de campo textual, si se debe visitar alguna función miembro del objeto de campo, etc. Si las direcciones coinciden repentinamente, cualquier código existente que dependa de estas claves podría romperse.

+0

"cosas como memset/memcpy etc. intentarían funcionar en regiones de memoria que no estaban en uso por los objetos vacíos", solo si pasaste una longitud incorrecta (es decir, no 0) a memset/memcpy. –

+0

@Tony: No estaba sugiriendo que 'sizeof (Empty)' sea '0', sino que dado' struct Foo {Empty e; int i; }; 'uno tenía' sizeof (Foo) == sizeof (int) 'lo que significaría guardar aproximadamente 4/8 bytes de memoria aquí (debido a la alineación) –

+0

@Steve: exactamente :-) - esa oración comienza con" sizeof (Empty)! = 0 ", lo que significa que cuando calcule el tamaño para la llamada memset/memcpy usando' sizeof (T) 'donde T está vacío, obtendría un valor distinto de cero ... –

1

Dado que Empty es un tipo POD, puede usar memcpy para sobrescribir su "representación", por lo que es mejor no compartirlo con otro objeto C++ o datos útiles.

Cuestiones relacionadas