Malloc vs new - різні накладки


110

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

Ми зазвичай використовуємо newі delete, але тут я використовую mallocі free. Це необхідно, тому що деякі компілятори будуть використовувати дані по-різному при newвикористанні, що призводить до помилок при передачі даних між різними платформами. Цього не відбувається malloc.

Це не відповідає нічого, що я знаю зі стандартних запитань newпроти malloc.

Яка різниця між новим / видаленням та malloc / free? натякає на ідею, що компілятор міг обчислити розмір об'єкта по-різному (але тоді, чому це відрізняється від використання sizeof?).

malloc & розташування new vs. new - досить популярне питання, але говорить лише про newвикористання конструкторів, де mallocнемає, що для цього не стосується.

як malloc розуміє вирівнювання? говорить про те, що пам'ять гарантовано належним чином узгоджена з будь-якою newабо про mallocце я вважав раніше.

Я здогадуюсь, що він неправильно діагностував власну помилку в минулому і виводив це newі mallocдавав різні кількості прокладки, що, напевно, я думаю, що це неправда. Але я не можу знайти відповіді в Google або в жодному попередньому запитанні.

Допоможи мені, StackOverflow, ти єдина моя надія!


33
+1 лише для дослідження різних потоків SO!
iammilind

7
+1 Легко одна з найкращих науково-дослідних робіт "допомога собі перед тим, як я попрошу інших", яку я бачив на ПЗ за довгий час. Бажаю, щоб я міг підтвердити це ще кілька разів.
WhozCraig

1
Чи передбачає код передачі, що дані вирівнюються певним чином, наприклад, що вони починаються на восьмибайтовій межі? Це може відрізнятися між собою mallocі new, як newв деяких середовищах виділяти блок, додає деякі дані на початок і повертає вказівник на місце одразу після цих даних. (Я погоджуюся з іншими, всередині блоку даних, mallocі newповинен використовувати той самий вид прокладки.)
Lindydancer

1
Нічого собі, я не очікував, що це питання буде таким популярним! @Lindydancer, я не думаю, що жодна 8-байтна межа не передбачається. Цікавий момент, хоча.
hcarver

1
Однією з причин використовувати один метод розподілу над іншим - коли "хтось інший" робить вивільнення об'єкта. Якщо цей "хтось інший" видаляє об'єкт, використовуючи безкоштовно, його потрібно виділити за допомогою malloc. (Випуск
накладки

Відповіді:


25

У ІІРК є одна прискіплива точка. mallocгарантовано поверне адресу, вирівняну для будь-якого стандартного типу. ::operator new(n)гарантовано повернути адресу, вирівняну для будь-якого стандартного типу не більше n , а якщо Tце не тип символу, new T[n]потрібно лише повернути адресу, вирівняну за T.

Але це актуально лише тоді, коли ви граєте з прийомами для впровадження, як-от використання кількох нижчих бітів вказівника для зберігання прапорів або іншим чином покладаючись на адресу, щоб мати більше вирівнювання, ніж це суворо потрібно.

Це не впливає на прокладку всередині об'єкта, яка обов'язково має точно такий же макет незалежно від того, як ви виділили пам'ять, яку він займає. Тому важко зрозуміти, як різниця може призвести до помилок при передачі даних.

Чи є якась ознака того, що автор цього коментаря думає про об’єкти на стеці чи в глобальних тонах, чи є, на його думку, вони "забиті як малок" чи "підбиті як нові"? Це може дати підказки, звідки виникла ідея.

Може бути , він збентежений, але , можливо, код він про розмову більше , ніж різниця між прямою 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

3

Макет об’єкта не може залежати від того, виділений він за допомогою mallocабо new. Вони обидва повертають один і той же тип вказівника, і коли ви передасте цей покажчик іншим функціям, вони не знають, як виділився об'єкт. sizeof *ptrпросто залежить від декларації ptr, а не від того , як вона була призначена.


3

Я думаю, ти маєш рацію. Прокладка робиться компілятором не newабо malloc. Зауваження щодо заміни застосовуватимуться навіть у тому випадку, якщо ви оголосили масив або структуру, не використовуючи newабо mallocвзагалі. У будь-якому випадку, хоча я бачу, наскільки різні реалізації newта mallocможуть викликати проблеми при перенесенні коду між платформами, я повністю не бачу, як вони можуть спричинити проблеми з передачею даних між платформами.


Раніше я припускав, що ви можете вважати newгарною обгорткою, mallocале, як видається з інших відповідей, це не зовсім правда. Здається, що консенсус полягає в тому, що прокладки повинні бути однаковими з будь-яким; Я думаю, що проблема з передачею даних між платформами виникає лише в тому випадку, коли ваш механізм передачі є помилковим :)
hcarver

0

Коли я хочу керувати компонуванням моєї простої старої структури даних, використовую компілятори MS Visual #pragma pack(1). Я припускаю, що така директива докомпілятора підтримується для більшості компіляторів, як, наприклад, gcc .

Це зумовлює вирівнювання всіх полів структур одна за одною, без порожніх пробілів.

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

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


Які ситуації існують, коли ви хочете контролювати структуру даних даних? Просто цікаво.
hcarver

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

gcc підтримує його, наприклад. в якій ситуації мені це було потрібно: обмін бінарними даними між двома різними формами пластин: обмін бінарним потоком між Windows та palmOS, між Windows та Linux. посилання про gcc: gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Стефан Ролланд

0

Це моя дика здогадка, звідки ця річ береться. Як ви вже згадували, проблема полягає в передачі даних через MPI.

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

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


Копіювання в масиви char може бути проблематичним - можливо, деякі ваші сердечники знаходяться на архітектурах мало-ендіанських, а деякі на великих-ендіанських (можливо, це не ймовірно, але можливо). Вам доведеться їх кодувати 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, ніж ми повинні, і слід використовувати typecast, інакше компілятор надасть вам помилку, і якщо ми використовуємо новий тип даних для розподілу пам'яті, ніж нам не потрібно набрати


1
Я думаю, вам слід спробувати ще більше аргументувати свою відповідь.
Карло

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