Malloc vs new - padding diferente

110

Estou revisando o código C ++ de outra pessoa para nosso projeto que usa MPI para computação de alto desempenho (10 ^ 5 - 10 ^ 6 núcleos). O código se destina a permitir a comunicação entre (potencialmente) diferentes máquinas em diferentes arquiteturas. Ele escreveu um comentário que diz algo como:

Normalmente usaríamos newe delete, mas aqui estou usando malloce free. Isso é necessário porque alguns compiladores preencherão os dados de maneira diferente quando newforem usados, levando a erros na transferência de dados entre plataformas diferentes. Isso não acontece com malloc.

Isso não se encaixa com nada que eu conheço de perguntas padrão newvs.malloc

Qual é a diferença entre new / delete e malloc / free? sugere a ideia de que o compilador poderia calcular o tamanho de um objeto de maneira diferente (mas então por que isso difere de usar sizeof?).

malloc & placement new vs. new é uma questão bastante popular, mas só fala sobre o newuso de construtores onde mallocnão, o que não é relevante para isso.

como o malloc entende o alinhamento? diz que a memória tem garantia de estar devidamente alinhada com um newou com o mallocque eu pensava anteriormente.

Meu palpite é que ele diagnosticou erroneamente seu próprio bug algum tempo no passado e deduziu isso newe mallocdeu diferentes quantidades de preenchimento, o que eu acho que provavelmente não é verdade. Mas não consigo encontrar a resposta no Google ou em nenhuma pergunta anterior.

Ajude-me, StackOverflow, você é minha única esperança!

Hcarver
fonte
33
+1 para a pesquisa de vários tópicos do SO sozinho!
iammilind
7
1 Facilmente, um dos melhores trabalhos de pesquisa do tipo "ajude-me antes de perguntar aos outros" que já vi no SO há MUITO tempo. Gostaria de poder votar mais algumas vezes.
WhozCraig
1
O código de transferência assume que os dados estão alinhados de alguma forma específica, por exemplo, que começam em um limite de oito bytes? Isso pode ser diferente entre malloce new, como newem alguns ambientes alocar um bloco, adiciona alguns dados ao início e retorna um ponteiro para um local logo após esses dados. (Concordo com os outros, dentro do bloco de dados, malloce newdevo usar o mesmo tipo de preenchimento.)
Lindydancer
1
Uau, eu não esperava que essa pergunta fosse tão popular! @Lindydancer, não acho que nenhum limite de 8 bytes seja assumido. Mas é um ponto interessante.
hcarver
1
Uma razão para usar um método de alocação em vez de outro é quando "outra pessoa" está liberando o objeto. Se esta "outra pessoa" deletar o objeto usando free, você deve alocar usando malloc. (O problema da almofada é uma
pista falsa

Respostas:

25

IIRC há um ponto exigente. malloctem a garantia de retornar um endereço alinhado a qualquer tipo de padrão. ::operator new(n)só tem garantia de retornar um endereço alinhado para qualquer tipo padrão não maior que n , e se Tnão for um tipo de caractere, então new T[n]é necessário retornar apenas um endereço alinhado para T.

Mas isso só é relevante quando você está jogando truques específicos de implementação, como usar os poucos bits inferiores de um ponteiro para armazenar sinalizadores ou depender do endereço para ter mais alinhamento do que o estritamente necessário.

Não afeta o preenchimento dentro do objeto, que necessariamente tem exatamente o mesmo layout, independentemente de como você alocou a memória que ocupa. Portanto, é difícil ver como a diferença pode resultar em erros na transferência de dados.

Há algum sinal do que o autor desse comentário pensa sobre objetos na pilha ou em globais, sejam em sua opinião "preenchidos como malloc" ou "preenchidos como novos"? Isso pode dar pistas sobre a origem da ideia.

Talvez ele está confuso, mas talvez o código que ele está falando é mais do que uma diferença clara entre malloc(sizeof(Foo) * n)vs new Foo[n]. Talvez seja mais como:

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

vs.

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

Ou seja, talvez ele esteja dizendo "Eu uso malloc", mas significa "Eu empacoto manualmente os dados em locais não alinhados em vez de usar uma estrutura". Na verdade, mallocnão é necessário para empacotar manualmente a estrutura, mas deixar de perceber isso é um grau menor de confusão. É necessário definir o layout dos dados enviados pela rede. Implementações diferentes preencherão os dados de maneira diferente quando a estrutura for usada.

Steve Jessop
fonte
Obrigado pelos pontos sobre alinhamento. Os dados em questão são uma matriz char, então suspeito que não seja uma coisa de alinhamento aqui, nem uma coisa de struct - embora esse tenha sido meu primeiro pensamento também.
hcarver
5
@Hbcdev: bem os chararrays nunca são preenchidos, então vou ficar com "confused" como explicação.
Steve Jessop
5

Seu colega pode ter tido o new[]/delete[]"magic cookie" em mente (esta é a informação que a implementação usa ao deletar um array). No entanto, isso não teria sido um problema se a alocação começando no endereço retornado por new[]fosse usada (ao contrário do alocador).

Fazer as malas parece mais provável. Variações em ABIs podem (por exemplo) resultar em um número diferente de bytes finais adicionados ao final de uma estrutura (isso é influenciado pelo alinhamento, considere também matrizes). Com o malloc, a posição de uma estrutura pode ser especificada e, portanto, mais facilmente transportável para uma ABI estrangeira. Essas variações são normalmente evitadas especificando o alinhamento e o empacotamento das estruturas de transferência.

justin
fonte
2
Foi o que pensei primeiro, o problema da "estrutura é maior do que a soma das partes". Talvez seja daí que sua ideia veio originalmente.
hcarver
3

O layout de um objeto não pode depender se ele foi alocado usando mallocou new. Ambos retornam o mesmo tipo de ponteiro, e quando você passa esse ponteiro para outras funções, eles não saberão como o objeto foi alocado. sizeof *ptrdepende apenas da declaração de ptr, não de como foi atribuído.

Barmar
fonte
3

Eu acho que você está certo. O preenchimento é feito pelo compilador não newou malloc. As considerações de preenchimento se aplicam mesmo se você declarar uma matriz ou estrutura sem usar newou mallocem tudo. Em qualquer caso, embora eu possa ver como diferentes implementações de newe mallocpodem causar problemas ao portar código entre plataformas, não consigo ver como eles podem causar problemas de transferência de dados entre plataformas.

John
fonte
Eu havia assumido que você poderia considerar newum bom invólucro para, mallocmas parece que, a partir de outras respostas, isso não é totalmente verdade. O consenso parece ser que o preenchimento deve ser o mesmo com qualquer um; Acho que o problema com a transferência de dados entre plataformas só surge se o seu mecanismo de transferência estiver com defeito :)
hcarver
0

Quando eu quero controlar o layout da minha estrutura de dados simples e antiga, eu uso os compiladores MS Visual #pragma pack(1). Suponho que essa diretiva de pré-compilador seja compatível com a maioria dos compiladores, como, por exemplo, gcc .

Isso tem como consequência o alinhamento de todos os campos das estruturas um atrás do outro, sem espaços vazios.

Se a plataforma na outra extremidade fizer o mesmo (isto é, compilou sua estrutura de troca de dados com um preenchimento de 1), então os dados recuperados em ambos os lados se ajustam bem. Portanto, nunca precisei brincar com malloc em C ++.

Na pior das hipóteses, eu teria considerado sobrecarregar o novo operador para que ele execute algumas coisas complicadas, em vez de usar malloc diretamente em C ++.

Stephane Rolland
fonte
Em que situações você deseja controlar o layout da estrutura de dados? Apenas curioso.
hcarver de
E alguém sabe de compiladores de suporte pragma packou semelhantes? Sei que não fará parte do padrão.
hcarver de
gcc suporta, por exemplo. em que situação eu precisava disso: compartilhar dados binários entre duas formas de placa diferentes: compartilhar fluxo binário entre windows e palmOS, entre windows e linux. links sobre gcc: gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Stephane Rolland
0

Este é o meu palpite de onde esta coisa está vindo. Como você mencionou, o problema é com a transmissão de dados por MPI.

Pessoalmente, para minhas estruturas de dados complicadas que desejo enviar / receber por MPI, sempre implemento métodos de serialização / desserialização que empacotam / descompactam tudo em / de uma matriz de caracteres. Agora, devido ao preenchimento, sabemos que esse tamanho da estrutura pode ser maior do que o tamanho de seus membros e, portanto, também é necessário calcular o tamanho não preenchido da estrutura de dados para sabermos quantos bytes estão sendo enviados / recebidos.

Por exemplo, se você deseja enviar / receber std::vector<Foo> Aatravés de MPI com a referida técnica, é errado assumir que o tamanho do array resultante de caracteres é A.size()*sizeof(Foo)em geral. Em outras palavras, cada classe que implementa métodos de serializar / desserializar também deve implementar um método que relata o tamanho do array (ou melhor ainda, armazena o array em um contêiner). Essa pode ser a razão por trás de um bug. De uma forma ou de outra, no entanto, isso não tem nada a ver com newvs, mallocconforme apontado neste tópico.

mmirzadeh
fonte
Copiar para matrizes char pode ser problemático - é possível que alguns de seus núcleos estejam em arquiteturas little-endian e alguns big-endian (talvez não seja provável, mas possível). Você teria que codificá-los em XDR ou algo assim, mas poderia apenas usar tipos de dados MPI definidos pelo usuário. Eles facilmente levam em conta o enchimento. Mas posso ver o que você está dizendo sobre a possível causa do mal-entendido - é o que estou chamando de problema da "estrutura é maior do que a soma de suas partes".
hcarver
Sim, definir os tipos de dados MPI é outra maneira correta de fazer isso. Bom ponto sobre endianness. Embora, eu realmente duvido que isso aconteça em clusters reais. De qualquer forma, pensei que se eles seguissem a mesma estratégia, isso poderia levar a bugs ...
mmirzadeh
0

Em c ++: a new palavra - chave é usada para alocar alguns bytes específicos de memória com relação a alguma estrutura de dados. Por exemplo, você definiu alguma classe ou estrutura e deseja alocar memória para seu objeto.

myclass *my = new myclass();

ou

int *i = new int(2);

Mas em todos os casos você precisa do tipo de dados definido (classe, estrutura, união, int, char, etc ...) e apenas os bytes de memória que são necessários para seu objeto / variável serão alocados. (ou seja, múltiplos desse tipo de dados).

Mas no caso do método malloc (), você pode alocar quaisquer bytes de memória e não precisa especificar o tipo de dados o tempo todo. Aqui você pode observá-lo em algumas possibilidades de malloc ():

void *v = malloc(23);

ou

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

ou

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

malloc é um tipo de função e new é um tipo de tipo de dados em c ++ em c ++, se usarmos malloc do que devemos e devemos usar typecast, caso contrário o compilador dá erro e se usarmos um novo tipo de dados para alocação de memória, não precisamos moldar

hk_043
fonte
1
Acho que você deveria tentar argumentar um pouco mais sobre sua resposta.
Carlo
Isso não parece resolver a questão de eles fazerem coisas diferentes com preenchimentos, que é o que eu estava perguntando acima.
Hcarver