Malloc vs new - acolchado diferente

110

Estoy revisando el código C ++ de otra persona para nuestro proyecto que usa MPI para computación de alto rendimiento (10 ^ 5 - 10 ^ 6 núcleos). El código está destinado a permitir las comunicaciones entre (potencialmente) diferentes máquinas en diferentes arquitecturas. Ha escrito un comentario que dice algo parecido a:

Normalmente usamos newy delete, pero aquí estoy usando mallocy free. Esto es necesario porque algunos compiladores rellenarán los datos de manera diferente cuando newse utilicen, lo que provocará errores en la transferencia de datos entre diferentes plataformas. Esto no sucede con malloc.

Esto no encaja con nada de lo que sé de las preguntas estándar newfrente a las mallocpreguntas.

¿Cuál es la diferencia entre new / delete y malloc / free? insinúa la idea de que el compilador podría calcular el tamaño de un objeto de manera diferente (pero entonces, ¿por qué eso difiere del uso sizeof?).

malloc y ubicación new vs new es una pregunta bastante popular, pero solo habla sobre el newuso de constructores donde mallocno lo hace, lo cual no es relevante para esto.

¿Cómo entiende malloc la alineación? dice que se garantiza que la memoria se alineará correctamente con cualquiera de los dos newo mallocque es lo que pensaba anteriormente.

Supongo que diagnosticó erróneamente su propio error en el pasado y lo dedujo newy le mallocdio diferentes cantidades de relleno, lo que creo que probablemente no sea cierto. Pero no encuentro la respuesta en Google ni en ninguna pregunta anterior.

Ayúdame, StackOverflow, ¡eres mi única esperanza!

hcarver
fuente
33
+1 solo para la investigación de varios hilos SO!
iammilind
7
+1 Fácilmente uno de los mejores trabajos de investigación "ayúdeme antes de preguntarle a los demás" que he visto en SO en mucho tiempo. Ojalá pudiera votar a favor de esto unas cuantas veces más.
WhozCraig
1
¿El código de transferencia asume que los datos están alineados de alguna manera específica, por ejemplo, que comienzan en un límite de ocho bytes? Esto podría diferir entre mallocy new, como newen algunos entornos asignan un bloque, agrega algunos datos al principio y devuelve un puntero a una ubicación justo después de estos datos. (Estoy de acuerdo con los demás, dentro del bloque de datos, mallocy newdebo usar el mismo tipo de relleno).
Lindydancer
1
¡Vaya , no esperaba que esta pregunta fuera tan popular! @Lindydancer, no creo que se asuma ningún límite de 8 bytes. Aunque es un punto interesante.
hcarver
1
Una razón para usar un método de asignación sobre otro es cuando "alguien más" está haciendo la liberación del objeto. Si este "alguien más" elimina el objeto usando free, debe asignar usando malloc. (Un problema de la almohadilla es una pista falsa.)
Lindydancer

Respuestas:

25

IIRC hay un punto delicado. mallocse garantiza que devolverá una dirección alineada para cualquier tipo estándar. ::operator new(n)solo se garantiza que devuelva una dirección alineada para cualquier tipo estándar no mayor que n , y si Tno es un tipo de carácter, new T[n]solo se requiere que devuelva una dirección alineada para T.

Pero esto solo es relevante cuando está jugando trucos específicos de implementación, como usar los pocos bits inferiores de un puntero para almacenar banderas, o confiar en la dirección para tener más alineación de la estrictamente necesaria.

No afecta el relleno dentro del objeto, que necesariamente tiene exactamente el mismo diseño, independientemente de cómo haya asignado la memoria que ocupa. Por lo tanto, es difícil ver cómo la diferencia podría resultar en errores en la transferencia de datos.

¿Hay alguna señal de lo que el autor de ese comentario piensa sobre los objetos en la pila o en los globales, ya sea que en su opinión estén "acolchados como malloc" o "acolchados como nuevos"? Eso podría dar pistas sobre el origen de la idea.

Tal vez está confundido, pero tal vez el código que está hablando es más que una diferencia recta entre malloc(sizeof(Foo) * n)vs new Foo[n]. Tal vez sea más como:

malloc((sizeof(int) + sizeof(char)) * n);

vs.

struct Foo { int a; char b; }
new Foo[n];

Es decir, tal vez esté diciendo "yo uso malloc", pero significa "empaqueto manualmente los datos en ubicaciones no alineadas en lugar de utilizar una estructura". En realidad, mallocno es necesario para empaquetar manualmente la estructura, pero no darse cuenta de eso es un grado menor de confusión. Es necesario definir el diseño de los datos enviados por cable. Las diferentes implementaciones rellenarán los datos de manera diferente cuando se use la estructura .

Steve Jessop
fuente
Gracias por los puntos sobre la alineación. Los datos en cuestión son una matriz de caracteres, por lo que sospecho que aquí no se trata de una alineación, ni de una estructura, aunque ese también fue mi primer pensamiento.
hcarver
5
@Hbcdev: bueno, las charmatrices nunca se rellenan, así que me quedo con "confuso" como explicación.
Steve Jessop
5

Es posible que su colega haya tenido new[]/delete[]en mente la cookie mágica (esta es la información que usa la implementación al eliminar una matriz). Sin embargo, esto no habría sido un problema si new[]se usara la asignación que comienza en la dirección devuelta por (a diferencia de la del asignador).

Empacar parece más probable. Las variaciones en las ABI podrían (por ejemplo) resultar en un número diferente de bytes finales agregados al final de una estructura (esto está influenciado por la alineación, también considere las matrices). Con malloc, se podría especificar la posición de una estructura y, por lo tanto, se podría transportar más fácilmente a una ABI extranjera. Estas variaciones normalmente se evitan especificando la alineación y el empaquetamiento de las estructuras de transferencia.

justin
fuente
2
Esto fue lo que pensé primero, el problema de "estructura es mayor que la suma de sus partes". Quizás de aquí es de donde surgió originalmente su idea.
hcarver
3

El diseño de un objeto no puede depender de si se asignó mediante malloco new. Ambos devuelven el mismo tipo de puntero, y cuando pasa este puntero a otras funciones, no sabrán cómo se asignó el objeto. sizeof *ptrdepende de la declaración de ptr, no de cómo se asignó.

Barmar
fuente
3

Creo que tienes razón. El relleno lo realiza el compilador, no newo malloc. Las consideraciones de relleno se aplicarían incluso si declarara una matriz o estructura sin usar newo mallocen absoluto. En cualquier caso, si bien puedo ver cómo las diferentes implementaciones newy mallocpodrían causar problemas al portar código entre plataformas, no veo cómo podrían causar problemas al transferir datos entre plataformas.

Juan
fuente
Anteriormente asumí que podría considerarlo newcomo un buen envoltorio para, mallocpero parece que por otras respuestas eso no es del todo cierto. El consenso parece ser que el acolchado debería ser el mismo con ambos; Creo que el problema con la transferencia de datos entre plataformas solo surge si su mecanismo de transferencia es defectuoso :)
hcarver
0

Cuando quiero controlar el diseño de mi estructura de datos antigua simple, utilizo compiladores de MS Visual #pragma pack(1). Supongo que dicha directiva de precompilador es compatible con la mayoría de los compiladores, como por ejemplo gcc .

Esto tiene la consecuencia de alinear todos los campos de las estructuras uno detrás del otro, sin espacios vacíos.

Si la plataforma del otro extremo hace lo mismo (es decir, compiló su estructura de intercambio de datos con un relleno de 1), entonces los datos recuperados en ambos lados encajan bien. Por lo tanto, nunca tuve que jugar con malloc en C ++.

En el peor de los casos, habría considerado sobrecargar el nuevo operador para que realice algunas cosas complicadas, en lugar de usar malloc directamente en C ++.

Stephane Rolland
fuente
¿Qué situaciones existen en las que desea controlar el diseño de la estructura de datos? Sólo curioso.
hcarver
¿Alguien sabe de compiladores compatibles pragma packo similares? Me doy cuenta de que no será parte del estándar.
hcarver
gcc lo admite, por ejemplo. en qué situación necesitaba eso: compartir datos binarios entre dos plataformas diferentes: compartir flujo binario entre windows y palmOS, entre windows y linux. enlaces sobre gcc: gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Stephane Rolland
0

Esta es mi loca suposición de de dónde viene esta cosa. Como mencionaste, el problema está en la transmisión de datos a través de MPI.

Personalmente, para mis complicadas estructuras de datos que quiero enviar / recibir a través de MPI, siempre implemento métodos de serialización / deserialización que empaquetan / descomprimen todo en / desde una matriz de caracteres. Ahora, debido al relleno, sabemos que ese tamaño de la estructura podría ser mayor que el tamaño de sus miembros y, por lo tanto, también es necesario calcular el tamaño sin relleno de la estructura de datos para saber cuántos bytes se envían / ​​reciben.

Por ejemplo, si desea enviar / recibir a std::vector<Foo> Através de MPI con dicha técnica, es incorrecto asumir que el tamaño de la matriz resultante de caracteres es A.size()*sizeof(Foo)en general. En otras palabras, cada clase que implementa métodos de serialización / deserialización, también debe implementar un método que informe el tamaño de la matriz (o mejor aún, almacenar la matriz en un contenedor). Esta podría convertirse en la razón detrás de un error. De una forma u otra, sin embargo, eso no tiene nada que ver con newvs malloccomo se señala en este hilo.

mmirzadeh
fuente
Copiar en matrices de caracteres puede ser problemático: es posible que algunos de sus núcleos estén en arquitecturas little-endian y algunos big-endian (tal vez no sea probable, pero posible). Tendría que codificarlos en XDR o algo así, pero podría usar tipos de datos MPI definidos por el usuario. Fácilmente tienen en cuenta el acolchado. Pero puedo ver lo que está diciendo acerca de la posible causa del malentendido: es lo que llamo el problema de "la estructura es mayor que la suma de sus partes".
hcarver
Sí, definir los tipos de datos MPI es otra forma correcta de hacerlo. Buen comentario sobre la endianidad. Aunque, realmente dudo que eso suceda en clústeres reales. De todos modos, pensé que si seguían la misma estrategia, esto podría generar errores ...
mmirzadeh
0

En c ++: la new palabra clave se usa para asignar algunos bytes particulares de memoria con respecto a alguna estructura de datos. Por ejemplo, ha definido alguna clase o estructura y desea asignar memoria para su objeto.

myclass *my = new myclass();

o

int *i = new int(2);

Pero en todos los casos, necesita el tipo de datos definido (clase, estructura, unión, int, char, etc.) y solo se asignarán los bytes de memoria que se requieren para su objeto / variable. (es decir, múltiplos de ese tipo de datos).

Pero en el caso del método malloc (), puede asignar cualquier byte de memoria y no necesita especificar el tipo de datos en todo momento. Aquí puedes observarlo en pocas posibilidades de malloc ():

void *v = malloc(23);

o

void *x = malloc(sizeof(int) * 23);

o

char *c = (char*)malloc(sizeof(char)*35);
Rahul Raina
fuente
-1

malloc es un tipo de función y new es un tipo de tipo de datos en c ++ en c ++, si usamos malloc, entonces debemos y debemos usar typecast; de lo contrario, el compilador le dará un error y si usamos un nuevo tipo de datos para la asignación de memoria, entonces no necesitamos encasillar

hk_043
fuente
1
Creo que deberías intentar argumentar un poco más tu respuesta.
Carlo
Esto no parece abordar la cuestión de que hagan cosas diferentes con los rellenos, que es lo que realmente estaba preguntando anteriormente.
hcarver