Malloc vs new - разные обивки


110

Я просматриваю чужой код C ++ для нашего проекта, который использует MPI для высокопроизводительных вычислений (10 ^ 5 - 10 ^ 6 ядер). Код предназначен для обеспечения связи между (потенциально) разными машинами на разных архитектурах. Он написал комментарий, в котором говорится примерно следующее:

Обычно мы использовали бы newи delete, но здесь я использую mallocи free. Это необходимо, потому что некоторые компиляторы по-разному дополняют данные при newиспользовании, что приводит к ошибкам при передаче данных между разными платформами. Этого не происходит с malloc.

Это не вписывается ни с чем я знаю от стандартных newпротив mallocвопросов.

В чем разница между new / delete и malloc / free? намекает на идею, что компилятор может вычислять размер объекта по-другому (но тогда почему это отличается от использования sizeof?).

malloc и размещение new vs. new - довольно популярный вопрос, но он говорит только об newиспользовании конструкторов там, где mallocнет, что не имеет отношения к этому.

как malloc понимает выравнивание? говорит, что память гарантированно будет правильно выровнена с любым newили, mallocкак я раньше думал.

Я предполагаю, что когда-то в прошлом он неправильно диагностировал свою собственную ошибку, вывел ее newи mallocдал разное количество отступов, что, по-моему, вероятно, неверно. Но я не могу найти ответ ни в Google, ни в каком-либо предыдущем вопросе.

Помогите мне, StackOverflow, ты моя единственная надежда!


33
+1 только за исследование различных SO потоков!
iammilind

7
+1 Легко одна из лучших исследовательских работ типа «помоги себе, прежде чем я попрошу других», которую я видел на SO за ДЛИТЕЛЬНОЕ время. Хотел бы я проголосовать за это еще несколько раз.
WhozCraig 08

1
Предполагает ли код передачи, что данные выровнены каким-либо определенным образом, например, что они начинаются с восьмибайтовой границы? Это может различаться между mallocи new, поскольку newв некоторых средах выделяют блок, добавляют некоторые данные в начало и возвращают указатель на местоположение сразу после этих данных. (Я согласен с другими, внутри блока данных mallocи newдолжен использовать такие же отступы.)
Lindydancer

1
Вау, я не ожидал, что этот вопрос станет таким популярным! @Lindydancer, я не думаю, что предполагается 8-байтовая граница. Интересный момент.
hcarver 08

1
Одна из причин использовать один метод распределения вместо другого - когда «кто-то другой» выполняет освобождение объекта. Если этот «кто-то другой» удаляет объект с помощью free, вы должны выделить его с помощью malloc. (Проблема с подушечкой
отвлекающий

Ответы:


25

IIRC есть один придирчивый момент. mallocгарантированно вернет адрес, выровненный для любого стандартного типа. ::operator new(n)гарантированно возвращает только адрес, выровненный для любого стандартного типа не больше n , а если Tне является символьным типом, то new T[n]требуется только вернуть адрес, выровненный для T.

Но это актуально только тогда, когда вы играете уловки, зависящие от реализации, например, используете несколько нижних битов указателя для хранения флагов или иным образом полагаетесь на то, что адрес имеет большее выравнивание, чем это строго необходимо.

Это не влияет на заполнение внутри объекта, который обязательно имеет точно такой же макет, независимо от того, как вы распределили память, которую он занимает. Так что сложно понять, как разница может привести к ошибкам при передаче данных.

Есть ли какие-либо признаки того, что автор этого комментария думает об объектах в стеке или глобальных объектах, независимо от того, являются ли они, по его мнению, «дополненными как malloc» или «дополненными как новые»? Это может дать ключ к разгадке того, откуда пришла идея.

Может быть , он смущен, но , возможно, код он о разговоре больше , чем разница между прямой malloc(sizeof(Foo) * n)против new Foo[n]. Может, это больше похоже на:

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

vs.

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

То есть, возможно, он говорит : «Я использую malloc», но означает: «Я вручную упаковываю данные в невыровненные места вместо использования структуры». На самом деле mallocне требуется для того, чтобы вручную упаковать структуру, но если вы не осознаете это, то это меньшая степень путаницы. Необходимо определить структуру данных, передаваемых по сети. Различные реализации будут подушечка данные по- разному , когда структура используется.


Спасибо за вопросы о выравнивании. Данные, о которых идет речь, представляют собой массив символов, поэтому я подозреваю, что здесь речь идет не о выравнивании и не о структуре, хотя это тоже была моя первая мысль.
hcarver

5
@Hbcdev: ну, charмассивы вообще никогда не дополняются, поэтому я буду придерживаться слова «запутанный» в качестве объяснения.
Стив Джессоп

5

Ваш коллега, возможно, имел new[]/delete[]в виду волшебный файл cookie (это информация, которую реализация использует при удалении массива). Однако это не было бы проблемой, если бы использовалось выделение, начинающееся с адреса, возвращенного new[]функцией (в отличие от распределителя).

Упаковка кажется более вероятной. Вариации в ABI могут (например) привести к другому количеству завершающих байтов, добавленных в конце структуры (на это влияет выравнивание, также учитывайте массивы). С помощью malloc можно указать положение структуры и, таким образом, упростить перенос ее в чужой ABI. Эти отклонения обычно предотвращаются путем задания выравнивания и упаковки структур переноса.


2
Это было то, что я сначала подумал, проблема «структура больше, чем сумма ее частей». Возможно, отсюда и родилась его идея.
hcarver 08

3

Макет объекта не может зависеть от того, был ли он выделен с помощью mallocили new. Оба они возвращают один и тот же тип указателя, и когда вы передаете этот указатель другим функциям, они не узнают, как был выделен объект. sizeof *ptrпросто зависит от объявления ptr, а не от того , как он был назначен.


3

Я думаю, вы правы. Заполнение выполняется компилятором, а не newили malloc. Замечания по заполнению будут применяться, даже если вы объявили массив или структуру без использования newили mallocвообще. В любом случае, хотя я вижу, как разные реализации newи mallocмогут вызывать проблемы при переносе кода между платформами, я совершенно не понимаю, как они могут вызывать проблемы при передаче данных между платформами.


Раньше я предполагал, что вы можете рассматривать его newкак хорошую оболочку, mallocно из других ответов кажется, что это не совсем так. Кажется, что консенсус заключается в том, что заполнение должно быть одинаковым с любым из них; Я думаю, что проблема с передачей данных между платформами возникает только в том случае, если ваш механизм передачи
неисправен

0

Когда я хочу управлять компоновкой моей простой старой структуры данных, я использую компиляторы MS Visual #pragma pack(1). Я полагаю, что такая директива прекомпилятора поддерживается большинством компиляторов, например, gcc .

Следствием этого является выравнивание всех полей структур одно за другим без пустых пространств.

Если платформа на другом конце делает то же самое (т. Е. Скомпилировала свою структуру обмена данными с заполнением 1), то данные, полученные с обеих сторон, подходят. Таким образом, мне никогда не приходилось играть с malloc в C ++.

В худшем случае я бы подумал о перегрузке оператора new, чтобы он выполнял некоторые хитрые вещи, вместо того, чтобы использовать malloc непосредственно в C ++.


В каких ситуациях вы хотите управлять макетом структуры данных? Просто любопытно.
hcarver

А кто-нибудь знает о поддерживающих компиляторах pragma packили подобных? Я понимаю, что это не будет частью стандарта.
hcarver

gcc, например, поддерживает его. в какой ситуации мне это нужно: совместное использование двоичных данных между двумя разными пластинами: совместное использование двоичного потока между окнами и PalmOS, между окнами и Linux. ссылки о gcc: gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Stephane Rolland

0

Это мое безумное предположение о том, откуда эта штука. Как вы упомянули, проблема связана с передачей данных по MPI.

Лично для моих сложных структур данных, которые я хочу отправлять / получать через MPI, я всегда реализую методы сериализации / десериализации, которые упаковывают / распаковывают все это в / из массива символов. Теперь, благодаря заполнению, мы знаем, что этот размер структуры может быть больше, чем размер ее членов, и, следовательно, также необходимо вычислить незаполненный размер структуры данных, чтобы мы знали, сколько байтов отправлено / получено.

Например, если вы хотите отправлять / получать std::vector<Foo> Aчерез MPI с помощью указанной техники, неверно предполагать, что размер результирующего массива символов A.size()*sizeof(Foo)в целом. Другими словами, каждый класс, реализующий методы сериализации / десериализации, также должен реализовывать метод, который сообщает размер массива (или, еще лучше, сохраняет массив в контейнере). Это могло стать причиной ошибки. Так или иначе, однако, это не имеет ничего общего с newvs, mallocкак указано в этой ветке.


Копирование в массивы символов может быть проблематичным - возможно, что некоторые из ваших ядер находятся на архитектуре с прямым порядком байтов, а некоторые с прямым порядком байтов (возможно, маловероятно, но возможно). Вам придется их XDR-кодировать или что-то в этом роде, но вы можете просто использовать определяемые пользователем типы данных MPI. Они легко учитывают набивку. Но я понимаю, что вы говорите о возможной причине недопонимания - это то, что я называю проблемой «структура больше, чем сумма ее частей».
hcarver

Да, определение типов данных MPI - еще один / правильный способ сделать это. Хорошее замечание о порядке байтов. Хотя я очень сомневаюсь, что это произойдет на реальных кластерах. В любом случае, я подумал, что если они будут следовать той же стратегии, это может привести к ошибкам ...
mmirzadeh

0

В c ++: new ключевое слово используется для выделения определенных байтов памяти по отношению к некоторой структуре данных. Например, вы определили некоторый класс или структуру и хотите выделить память для этого объекта.

myclass *my = new myclass();

или

int *i = new int(2);

Но во всех случаях вам нужен определенный тип данных (класс, структура, объединение, int, char и т. Д.), И будут выделены только те байты памяти, которые необходимы для его объекта / переменной. (т. е. кратные этому типу данных).

Но в случае метода malloc () вы можете выделить любые байты памяти, и вам не нужно постоянно указывать тип данных. Здесь вы можете увидеть это в нескольких возможностях malloc ():

void *v = malloc(23);

или

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

или

char *c = (char*)malloc(sizeof(char)*35);

-1

malloc - это тип функции, а new - это тип типа данных в c ++ в c ++, если мы используем malloc, чем должны, и должны использовать приведение типов, иначе компилятор выдаст вам ошибку, и если мы используем новый тип данных для выделения памяти, чем нам не нужно приводить к типу


1
Я думаю, вам стоит попытаться еще немного аргументировать свой ответ.
Карло

Это, похоже, не решает вопрос о том, что они делают разные вещи с отступами, о чем я действительно спрашивал выше.
hcarver
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.