Я кидаю результат малок?


2406

У цьому питанні хтось запропонував у коментарі, що я не повинен наводити результат malloc, тобто

int *sieve = malloc(sizeof(int) * length);

а не:

int *sieve = (int *) malloc(sizeof(int) * length);

Чому це було б так?


222
Крім того, більш доцільним є написання решета = malloc (sizeof * sieve * length);
Вільям Перселл


3
Тут відповіді - мінне поле однобічності. Відповідь "це залежить". Це не обов'язково, щоб програма працювала. Однак деякі стандарти кодування цього вимагають ... Наприклад, див. Стандарт кодування CERT C
Dr. Person Person II

Як не дивно, всі згодні, що ви не граєте NULL. (можливо, тому C ++ введено nullptr: C ++ не дозволяє явних вказівки)
Sapphire_Brick,

Можна, але цього не потрібно. Але вам потрібно в C ++.
Жодного тіла

Відповіді:


2216

Ні ; ви не кидаєте результат, оскільки:

  • Це непотрібно, оскільки void *автоматично і безпечно просувається до будь-якого іншого типу вказівника в цьому випадку.
  • Це додає безладу коду, касти читаються не дуже просто (особливо якщо тип вказівника довгий).
  • Це змушує повторити себе, що взагалі погано.
  • Він може приховати помилку, якщо ви забули включити <stdlib.h>. Це може спричинити збої (або, що ще гірше, не спричинити збої до подальшого шляху в якійсь абсолютно іншій частині коду). Поміркуйте, що станеться, якщо покажчики та цілі числа мають різний розмір; то ви приховуєте попередження, передаваючи і може втратити біти поверненої адреси. Примітка: на C99 неявні функції відійшли від C, і ця точка більше не є актуальною, оскільки немає автоматичного припущення про повернення незадекларованих функцій int.

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

Також зауважте, як зазначають коментатори, що вище сказано про пряму C, а не C ++. Я дуже твердо вірю в С та С ++ як окремі мови.

Щоб додати далі, ваш код непотрібно повторювати інформацію про тип ( int), що може спричинити помилки. Краще скасувати посилання на покажчик, який використовується для зберігання повернутого значення, щоб "заблокувати" два разом:

int *sieve = malloc(length * sizeof *sieve);

Це також пересуває на lengthпередню частину для збільшення видимості та скидає зайві дужки sizeof; вони потрібні лише тоді, коли аргумент - це ім'я типу. Багато людей, здається, не знають (або ігнорують) це, що робить їх код більш багатослівним. Пам'ятайте: sizeofце не функція! :)


Під час переміщення lengthна фронт може збільшити видимість у деяких рідкісних випадках, також слід звернути увагу, що в загальному випадку вираз слід краще написати так:

int *sieve = malloc(sizeof *sieve * length);

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

Порівняйте: malloc(sizeof *sieve * length * width)порівняно malloc(length * width * sizeof *sieve)з другим може переповнювати типи length * widthколи widthі lengthменше size_t.


21
Будь ласка, подумайте над оновленням відповіді. Акторський склад вже не є небезпечним, і повторення себе не обов'язково є поганою справою (надмірність може допомогти зловити помилки).
н. 'займенники' м.

11
Компілятори змінилися. Сучасний компілятор попередить вас про відсутність заявки про malloc.
н. 'займенники' м.

55
@nm Добре. Я думаю, що погано вважати, що кожен, хто читає тут, має певного компілятора. Крім того, оскільки C11 відсутня вся концепція "неявної функції", я цього не знав. І все-таки я не бачу сенсу в додаванні безглуздого складу. Ви також робите int x = (int) 12;лише для того, щоб зрозуміти речі?
розмотайте

22
@nm, якщо явне введення недійсного вказівника "допомогло" вирішити помилку, ви, швидше за все, зіткнулися з невизначеною поведінкою, що означало б, що програма, про яку йдеться, має набагато гірший, нерозкритий помилку, до якого ви ще не зіткнулися. І одного дня, холодним зимовим вечором, ви повернетесь додому з роботи, щоб знайти свою сторінку GitHub, заповнену повідомленнями про випуски скарг на демонів, що вилітають з ніс користувачів
Braden Best

12
@unwind Навіть я з вами згоден, (int)12не порівнянний. 12 єint , кидок робить просто нічого. Вилучення malloc()є void *, а не тип покажчика, на який прикинуто. (Якщо це не так void *. Тож аналогією (int)12було б (void*)malloc(…)те, про що ніхто не обговорює.)
Амін Негм-Авад

376

В C не потрібно вводити повернене значення malloc. Вказівник на недійсність, що повертається, mallocавтоматично перетворюється на правильний тип. Однак, якщо ви хочете, щоб ваш код компілювався з компілятором C ++, необхідний склад. Кращою альтернативою серед громади є використання наступного:

int *sieve = malloc(sizeof *sieve * length);

що додатково звільняє вас від турботи про зміну правої частини виразу, якщо коли-небудь ви змінюєте тип sieve.

Закиди погані, як вказували люди. Особливо кидає покажчик.


71
@MAKZ Я б заперечував, що malloc(length * sizeof *sieve)це виглядає як sizeofзмінна, тому я думаю, що malloc(length * sizeof(*sieve))це читабельніше.
Майкл Андерсон

21
І malloc(length * (sizeof *sieve))ще читабельніше. ІМХО.
Toby Speight

19
@Michael Anderson вимкніть (), зауважте, що запропонований вами стиль змінив порядок. Враховуйте, коли кількість елементів обчислюється як length*width, зберігаючи sizeofперше в цьому випадку, гарантує, що множення відбувається принаймні size_tматематикою. Порівняйте malloc(sizeof( *ptr) * length * width)порівняно malloc(length * width * sizeof (*ptr))- 2-й може переповнювати, length*widthколи width,lengthменші типи, які size_t.
chux

3
@chux це не очевидно, але відповідь відредаговано так, що мій коментар є менш доречним - оригінальна пропозиція булаmalloc(sizeof *sieve * length)
Майкл Андерсон

12
C не є C ++. Прикидаючись, що вони є, врешті-решт це призведе до плутанини і смутку. Якщо ви використовуєте C ++, тоді також може бути поганим показник стилю C (якщо ви не використовуєте дуже старий компілятор C ++). І static_cast>()(або reinterpret_cast<>()) не сумісний ні з яким діалектом С.
Девід К.

349

Ви робите роль, тому що:

  • Це робить ваш код більш портативним між C і C ++, і як показує досвід SO, дуже багато програмістів стверджують, що вони пишуть на C, коли вони справді пишуть на C ++ (або C плюс локальні розширення компілятора).
  • Якщо цього не зробити, можна приховати помилку : відзначте всі приклади ПУ, що плутають, коли писати type *проти type **.
  • Ідея про те, що це не дозволяє вам помітити, що вам не вдалося до #includeвідповідного файлу заголовка, не вистачає лісу для дерев . Це те саме, що говорити "не хвилюйся про те, що ти не зміг попросити компілятора поскаржитися на те, що не бачив прототипів - це прискіпливий stdlib.h НАДЕЙСЬКЕ важливо пам'ятати!"
  • Це змушує додаткову пізнавальну перехресну перевірку . Він (передбачуваний) бажаний тип розміщується поруч із арифметикою, яку ви робите для необмеженого розміру цієї змінної. Б'юсь об заклад, що ви могли б провести дослідження SO, яке показує, що malloc()помилки ловляться набагато швидше, коли є акторський склад. Як і у твердженнях, примітки, які розкривають помилки щодо намірів.
  • Повторення себе таким чином, що машина може перевірити, часто є чудовою ідеєю. Насправді, ось що є твердженням, і таке використання акторського складу - це твердження. Твердження як і раніше є найбільш загальною методикою для правильного кодування, оскільки Тьюрінг придумав цю ідею стільки років тому.

39
@ulidtko Якщо ви цього не знали, можна написати код, який компілюється і як C, і як C ++. Насправді більшість файлів заголовків схожі на це, і вони часто містять код (макроси та вбудовані функції). Мати .c/ .cppфайл для компіляції як обидва, не дуже корисно, але один випадок додає throwпідтримку C ++, коли компілюється з компілятором C ++ (але return -1;коли компілюється з компілятором C, або будь-яким іншим).
hyde

37
Якби хто-небудь мав дзвінки inlock у заголовку, я не був би вражений, #ifdef __cplusplus та extern "C" {} для цього завдання, не додаючи додаткові касти.
паульм

15
Ну, точка 1 не має значення, оскільки C! = C ++, інші пункти також тривіальні, якщо ви використовуєте змінну у своєму mallocдзвінку: char **foo = malloc(3*sizeof(*foo));якщо цілком повне підтвердження: 3 покажчики на покажчики char. потім петлю і робимо foo[i] = calloc(101, sizeof(*(foo[i])));. Виділіть масив з 101 символів, акуратно ініціалізованих до нулів. Не потрібен акторський склад. поміняйте декларацію на unsigned charбудь-який інший тип, з цього питання, і ви все ще добрі
Elias Van Ootegem

34
Коли я подумав, що дістав його, ось воно і приходить! Фантастична відповідь. Тут вперше в StackOverflow я поставив +1 дві протилежні відповіді! +1 Ні, ви не граєте, і +1 Так, ви робите! ЛОЛ. Ви, хлопці, приголомшливі. І для мене, і для учнів я зробив свою думку: я ролю акторську роль. Вигляд помилок, які студенти роблять, легше помічаються під час кастингу.
Доктор Беко,

15
@ Левшенко: Повторення себе способом, який не можна перевірити машиною, ані місцевою інспекцією, є поганим. Повторення себе способами, які можна перевірити такими засобами, є менш поганим. Враховуючи struct Zebra *p; ... p=malloc(sizeof struct Zebra);, malloc не може уникнути дублювання інформації про тип p, але ні компілятор, ні локальна перевірка коду не виявлять жодних проблем, якщо один тип змінився, а інший - ні. Змініть код, p=(struct Zebra*)malloc(sizeof struct Zebra);і компілятор почне збиватися, якщо тип анотації не збігається p, і місцева інспекція виявить ...
supercat

170

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

#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif

Таким чином, ви все ще можете написати це дуже компактно:

int *sieve = NEW(int, 1);

і він буде компілювати для C і C ++.


17
Оскільки ви все одно використовуєте макрос, чому б ви не використовували його newу визначенні C ++?
Хосам Алі

63
Тому що для цього немає підстав. В основному це стосується програм C, які компілюються за допомогою компілятора C ++. Якщо ви збираєтесь використовувати "нове", єдине, що ви отримаєте - це проблеми. Тоді вам також потрібен макрос безкоштовно. І вам потрібен макрос, щоб звільнити масив, диференціація якого не існує в C.
quinmars

8
Не кажучи вже про те, що ви не звільняєте пам’ять, але, можливо, бібліотека C, яку ви використовуєте, і т. Д. Багато можливих проблем без будь-якого посилення.
квінмар

86
@Hosam: Так, це точно. Якщо ви використовуєте, newви повинні використовувати, deleteа якщо ви використовуєте, malloc()ви повинні і ви free(). Ніколи їх не змішуйте.
Graeme Perrow

17
Якщо хтось буде використовувати цей підхід, виклик макросу NEW, мабуть, погана ідея, оскільки ресурс ніколи не повертається за допомогою delete(або DELETE), тому ви змішуєте свій словниковий запас. Натомість називати його MALLOC, а точніше CALLOCв цьому випадку, було б більше сенсу.
ма

139

З Вікіпедії :

Переваги кастингу

  • Включаючи команду, можливо, програма C або функція можуть компілювати як C ++.

  • У ролях передбачені версії malloc до 1989 року, які спочатку повернули знак *.

  • Кастинг може допомогти розробнику виявити невідповідності у розмірі типів у разі зміни типу вказівника призначення, особливо якщо вказівник оголошено далеко від виклику malloc () (хоча сучасні компілятори та статичні аналізатори можуть попередити про таку поведінку, не вимагаючи відкидання).

Недоліки лиття

  • Відповідно до стандарту ANSI C, акторський склад є зайвим.

  • Додавання амплітуди може замаскувати невключення заголовка stdlib.h, в якому знайдено прототип для malloc. За відсутності прототипу для malloc, стандарт вимагає, щоб компілятор C припустив, що malloc повертає int. Якщо немає введення, видається попередження, коли це ціле число призначено покажчику; однак, при ролях це попередження не видається, приховуючи помилку. У певних архітектурах та моделях даних (наприклад, LP64 на 64-бітних системах, де довгі та покажчики є 64-бітовими, а int - 32-розрядні), ця помилка може насправді призвести до невизначеного поведінки, оскільки неявно оголошений malloc повертає 32- бітове значення, тоді як фактично визначена функція повертає 64-бітове значення. Залежно від умов виклику та компонування пам’яті, це може призвести до розбиття стека. Ця проблема рідше залишиться непоміченою у сучасних компіляторах, оскільки вони рівномірно створюють попередження про те, що використовується незадекларована функція, то попередження все одно з’явиться. Наприклад, поведінка за замовчуванням GCC полягає в тому, щоб показувати попередження, яке читає "несумісне неявне декларування вбудованої функції", незалежно від того, присутній атрибут чи ні.

  • Якщо тип вказівника змінено при його оголошенні, також може знадобитися змінити всі рядки, де викликується та передається malloc.

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

тобто: Якщо вам потрібно скласти програму C як C ++ (Хоча це окрема мова), ви повинні вказати результат використання malloc.


1
Що означає " Кастинг може допомогти розробнику виявити невідповідності у розмірі типів, чи повинен змінюватися тип призначення вказівника, особливо якщо вказівник оголошено далеко від malloc()виклику "? Чи можете ви навести приклад?
Spikatrix

3
@CoolGuy: див. Попередній коментар до іншої відповіді . Але зауважте, що p = malloc(sizeof(*p) * count)ідіома автоматично сприймає зміни типу, тому вам не доведеться отримувати попередження та йти щось змінювати. Таким чином, це не є реальною перевагою порівняно з найкращою альтернативою для не-кастингу.
Пітер Кордес

7
Це правильна відповідь: Є плюси і мінуси, і це зводиться до питання смаку (якщо код не повинен складатися як C ++ - тоді команда є обов'язковою).
Пітер - Відновіть Моніку

3
Пункт 3 є суперечливим, оскільки якщо тип вказівника змінено при його оголошенні, слід перевіряти кожен екземпляр malloc, realloc та вільного inolving цього типу. Кастинг змусить вас зробити саме це.
Michaël Roy

104

У C ви можете неявно перетворити voidвказівник на будь-який інший вид вказівника, тому видання не потрібне. Використання одного може підказати випадковому спостерігачеві, що є якась причина, чому потрібна така, яка може ввести в оману.


100

Ви не кидаєте результат malloc, тому що це додає безглуздого безладу вашому коду.

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

Деякі коментарі:

  • Недійсний покажчик може бути перетворений в / з будь-якого іншого типу вказівника без явного виступу (C11 6.3.2.3 та 6.5.16.1).

  • C ++, однак, не дозволить неявного введення між void*і іншим типом вказівника. Так що в C ++, акторський склад був би правильним. Але якщо ви програмуєте на C ++, вам слід використовувати, newа не malloc (). І ніколи не слід компілювати код C за допомогою компілятора C ++.

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

  • Якщо компілятор C не може знайти функцію, оскільки ви забули включити заголовок, ви отримаєте помилку компілятора / посилання з цього приводу. Тож якщо ви забули включити, <stdlib.h>що це не велике, ви не зможете створити свою програму.

  • На стародавніх компіляторах, які дотримуються версії стандарту, якому більше 25 років, забуття про включення <stdlib.h>призведе до небезпечної поведінки. Тому що в тому стародавньому стандарті функції без видимого прототипу неявно перетворювали тип повернення int. Відкриття результату від malloc явно дозволить приховати цю помилку.

    Але це справді не питання. Ви не використовуєте 25-річний комп’ютер, тож чому б ви використовували компілятор 25 років?


9
"безглуздий безлад" - це зневажлива гіпербола, яка має тенденцію зірвати будь-яку можливість переконати тих, хто вже не згоден з вами. Акторський склад, безумовно, не безглуздий; Відповіді Рона Берка та Каза доводять аргументи на користь кастингу, з яким я дуже згоден. Чи важать ці проблеми більше, ніж ви, про які ви згадуєте, це розумне питання. На мене, ваші занепокоєння виглядають відносно незначними порівняно з їхніми.
Дон Хетч

"Недійсний покажчик може бути перетворений в / з будь-якого іншого типу вказівника без явного виступу", не підтримується в 6.3.2.3. Можливо, ви думаєте про "вказівник на будь-який тип об'єкта"? "недійсний покажчик" і "вказівник на функцію" не такі легко конвертовані.
chux

Дійсно, посилання було неповним. Відповідною частиною для "імпліцитності" є правило простого присвоєння 6.5.16.1. "один операнд - вказівник на тип об'єкта, а інший - вказівник на кваліфіковану або некваліфіковану версію void". Цю посилання я додав у відповідь для повноти.
Лундін

91

У C ви отримуєте неявне перетворення з void *будь-якого іншого (даних) покажчика.


6
@Jens: Гаразд, можливо, правильніше формулювання - це "неявна конверсія". Як і використання інтегральної змінної у виразі з плаваючою комою.
EFraim

@EFraim Це насправді призвело б до акторського складу, а до цього - неявного.
Божевільний фізик

71

Передавати значення, що повертається, malloc()зараз не потрібно, але я хотів би додати один момент, який, здається, ніхто не зазначив:

У стародавні часи, тобто до ANSI C забезпечує void *як загальний тип покажчиків, char *є тип для такого використання. У такому випадку виступ може закрити попередження компілятора.

Довідка: C FAQ


2
Закриття попереджень компілятора - погана ідея.
Альберт ван дер Хорст

8
@AlbertvanderHorst Не якщо ви це робите, вирішуючи точну проблему, попередження є про те, щоб вас попередити.
Ден Бешард

@Dan. Якщо під вирішенням точної проблеми мається на увазі переписання підпрограми для повернення сучасних типів ANSI C замість char *, я згоден. Я б не закликав це закривати компілятор. Не піддавайтеся менеджерам, які наполягають на тому, що немає попереджень компілятора, а не використовуйте їх для кожної перекомпіляції для пошуку можливих проблем. Groetjes Albert
Альберт ван дер Хорст

53

Тільки додаючи свій досвід, вивчаючи комп'ютерну техніку, я бачу, що два-три професора, яких я бачив, як писали на С, завжди кидають маловір, проте той, кого я запитав (з величезним резюме та розумінням С), сказав мені, що це абсолютно непотрібно, але використовувались лише для того, щоб бути абсолютно конкретними, і вбивати учнів у менталітет як абсолютно специфічний. По суті, кастинг нічого не змінить у тому, як він працює, він виконує саме те, що він каже, виділяє пам'ять, а кастинг не впливає на нього, ви отримуєте ту саму пам’ять і навіть якщо помилково відкинете її на щось інше (і якось ухиляєтесь від компілятора помилки) C отримає доступ до нього аналогічно.

Редагувати: Кастинг має певний момент. Коли ви використовуєте позначення масиву, згенерований код повинен знати, скільки місць пам'яті йому потрібно заздалегідь досягти початку наступного елемента, це досягається за допомогою кастингу. Таким чином, ви знаєте, що для подвійного ви йдете на 8 байт вперед, а для int - 4 і так далі. Таким чином, це не має ефекту, якщо ви використовуєте позначення вказівника, у позначенні масиву це стає необхідним.


3
За винятком вищезгаданих випадків, команда може приховувати помилки та ускладнювати аналіз для компілятора чи статичного аналізатора.
Лундін

2
"По суті, кастинг нічого не змінить у тому, як він працює". Передача типу відповідності не повинна нічого змінити, але якщо тип вару зміниться, а групування більше не збігаються, можуть виникнути проблеми? IWO, тип cast і var повинні підтримуватися синхронізовано - двічі роботи з технічного обслуговування.
chux

Я бачу, чому професори віддають перевагу кастингу. Кастинг може бути корисним з навчальної точки зору, коли він передає інформацію інструктору, і код студента не потрібно підтримувати - код його викидання. Однак з точки зору кодування, експертної оцінки та обслуговування - p = malloc(sizeof *p * n);це так просто і краще.
chux

53

Не обов'язково передавати результати malloc, оскільки вони повертаються void*, і а void*може бути вказано на будь-який тип даних.


34

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

Однак, якщо ви хочете, щоб той самий код працював ідеально сумісним на платформі C ++, яка не підтримує неявне перетворення, вам потрібно зробити набір даних, тому все залежить від зручності використання.


2
Це не звичайний випадок використання для компіляції одного джерела як C, так і C ++ (на відміну, скажімо, від використання файлу заголовка, що містить декларації для з'єднання C і C ++ коду разом). Використання mallocта друзів у C ++ є хорошим попереджувальним знаком про те, що він заслуговує на особливу увагу (або переписання на C).
Toby Speight

1
"Вказівник недійсності - це загальний покажчик" -> "Показник порожнечі - це загальний вказівник об'єкта ". Розміри покажчиків функцій можуть перевищувати void *, тому void *недостатньо для зберігання функціонального вказівника.
chux

мій намір цього рядка був таким же, але все одно дякую @chux за пропозицію.
Endeavor

33

Про це говорить посібник з бібліотеки GNU C :

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

І справді стандарт ISO C11 (p347) говорить так:

Вказівник, повернутий у разі успішного розподілу, є відповідним чином вирівняним, щоб він міг бути призначений покажчиком на будь-який тип об'єктів з основними вимогами вирівнювання, а потім використовуватися для доступу до такого об’єкта або масиву таких об'єктів у виділеному просторі (до простір явно розміщений)


31

Повертається тип - недійсний *, який може бути переданий до потрібного типу вказівника даних, щоб бути відкинутим.


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

але лише в тому випадку, якщо вам потрібно знеструмлювати його "на льоту", якщо ви створили змінну, вона замість цього буде безпечно та автоматично перетворена в ефективний тип змінної, без лиття (в С).
Феррарезі

28

У мові C недійсним покажчиком може бути призначений будь-який вказівник, саме тому не слід використовувати тип набору. Якщо ви хочете виділити "безпечне" виділення, я можу порекомендувати наступні макрофункції, які я завжди використовую в своїх проектах C:

#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)

З цим на місці ви можете просто сказати

NEW_ARRAY(sieve, length);

Для нединамічних масивів третім макросом функції must-have є

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])

що робить петлі масиву безпечнішими та зручнішими:

int i, a[100];

for (i = 0; i < LEN(a); i++) {
   ...
}

"покажчик недійсності може бути призначений будь-якому вказівнику об'єкта" Покажчики функцій - це ще одна проблема, хоча і не malloc()одна.
chux

Призначення void*покажчика функції до / з нього може втратити інформацію, тому "недійсний покажчик може бути призначений будь-якому вказівнику" є проблемою в цих випадках. Призначення a void*, від malloc() будь-якого вказівника об'єкта не є проблемою.
chux

doПетля коментарі , пов'язані з макросами , що включають цикл ще , що цікаво , від заголовка питання. Видалення цього коментаря. Пізніше також знімемо цю.
chux

27

Це залежить від мови програмування та компілятора. Якщо ви використовуєте mallocв C, немає необхідності вводити його, оскільки він автоматично вводить роль. Однак якщо ви використовуєте C ++, вам слід ввести cast, тому що mallocповерне void*тип.


1
Функція malloc також повертає недійсний покажчик в C, але правила мови відрізняються від C ++.
Серпень Карлстром

16

Люди, звикли до GCC та Clang, розбещені. Там все не так добре.

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

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

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

YMMV.

Я, як правило, вважаю, що кидати малалок як оборонну операцію. Не симпатичний, не ідеальний, але загалом безпечний. (Чесно кажучи, якщо ви не включили stdlib.h, тоді у вас набагато більше проблем, ніж при литті malloc!).


15

Я ставлю в групу просто, щоб показати несхвалення некрасивого отвору в системі типів, який дозволяє такий код, як наведений нижче фрагмент, збирати без діагностики, хоча жодні касти не використовуються, щоб призвести до поганої конверсії:

double d;
void *p = &d;
int *q = p;

Я б хотів, щоб його не існувало (а його немає в C ++), і тому я виступаю. Це репрезентує мій смак та мою політику програмування. Я не тільки кидаю вказівник, але ефективно, віддаю бюлетень і виганяю демонів дурості . Якщо я насправді не можу вигнати дурість , то, принаймні, дозвольте висловити бажання зробити це жестом протесту.

Насправді, хороша практика полягає в тому, щоб обернути malloc(і дружити) функціями, які повертаються unsigned char *, і взагалі ніколи не використовувати void *у своєму коді. Якщо вам потрібен загальний вказівник на будь-який об’єкт, використовуйте char *або unsigned char *і виконайте касти в обох напрямках. Один релаксації , які можна побавитися, можливо, використовує такі функції , як memsetі memcpyбез зліпків.

Що стосується теми кастингу та сумісності C ++, якщо ви пишете свій код так, що він компілюється як C, так і C ++ (у такому випадку вам потрібно передати повернене значення mallocпри призначенні його чомусь іншому, ніж void *), ви можете зробити дуже корисним Реч для себе: ви можете використовувати макроси для кастингу, які перекладаються на касти у стилі C ++ при компілюванні як C ++, але зменшують до символу C при компілюванні як C:

/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

Якщо ви дотримуєтесь цих макросів, то простий grepпошук вашої кодової бази за цими ідентифікаторами покаже вам, де всі ваші трансляції, тож ви зможете перевірити, чи не вказано жодне з них.

Тоді, вперед, якщо ви регулярно компілюєте код з C ++, він буде примусово використовувати відповідний склад. Наприклад, якщо ви використовуєте strip_qualтільки для видалення constабо volatile, але зміни програми таким чином , що перетворення типу в даний час бере участь, ви отримаєте діагностики, і ви повинні будете використовувати комбінацію зліпків , щоб отримати бажане перетворення.

Щоб допомогти вам дотримуватися цих макросів, компілятор GNU C ++ (не C!) Має прекрасну особливість: додаткову діагностику, яка виробляється для всіх випадків кастингу стилю C.

     -Старий стиль (лише C ++ та Objective-C ++)
         Попередьте, якщо використовується старий стиль (C-стиль), призначений для недійсного типу
         в рамках програми C ++. У ролях нового стилю (dynamic_cast,
         static_cast, reinterpret_cast та const_cast) менш вразливі
         до ненавмисних ефектів і набагато простіше шукати.

Якщо ваш код C компілюється як C ++, ви можете використовувати цю -Wold-style-castопцію, щоб дізнатися про всі виникнення (type)синтаксису кастингу, які можуть повзати в код, і продовжити цю діагностику, замінивши його відповідним вибором серед вищевказаних макросів (або комбінування, якщо необхідно).

Ця обробка перетворень є найбільшим окремим технічним обгрунтуванням роботи в "чистому С": комбінованому діалекті С і С ++, що, у свою чергу, технічно виправдовує викид зворотного значення malloc.


Як вказували інші, я зазвичай рекомендую не змішувати код C і C ++. Однак якщо у вас є вагомі підстави це зробити, то макроси можуть бути корисні.
Phil1970

@ Phil1970 Все написано в одному згуртованому діалекті, який, можливо, є переносним до компіляторів C і C ++, і використовує деякі можливості C ++. Це повинно бути складено як C ++, або ж все - як C.
Каз

Тобто, що я намагався сказати в попередньому коментарі, це те, що змішування C і C ++ не відбувається. Завдання полягає в тому, щоб код був зібраний у вигляді C або весь компільований як C ++.
Каз

15

Найкраще робити програмування на C, коли це можливо:

  1. Зробіть компіляцію вашої програми через компілятор C із увімкненими попередженнями -Wallта виправте всі помилки та попередження
  2. Переконайтесь, що немає змінних, оголошених як auto
  3. Потім компілюйте його за допомогою компілятора C ++ з -Wallта -std=c++11. Виправте всі помилки та попередження.
  4. Тепер знову компілюйте, використовуючи компілятор C. Тепер ваша програма повинна збиратись без будь-якого попередження та містити менше помилок.

Ця процедура дозволяє скористатися суворою перевіркою типу C ++, зменшивши, таким чином, кількість помилок. Зокрема, ця процедура змушує вас включити stdlib.hабо ви отримаєте

malloc не було задекларовано в межах цього обсягу

а також змушує вас кинути результат mallocабо ви отримаєте

недійсна конверсія від void*доT*

або будь-який ваш цільовий тип.

Єдині переваги від написання на C замість C ++ я можу знайти

  1. C має добре вказаний ABI
  2. C ++ може генерувати більше коду [винятки, RTTI, шаблони, поліморфізм часу виконання ]

Зауважте, що в ідеальному випадку другий мінус повинен зникнути при використанні підмножини, спільної для C разом зі статичною поліморфною ознакою.

Для тих, хто вважає суворі правила С ++ незручними, ми можемо використовувати функцію C ++ 11 з виведеним типом

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...

18
Використовуйте компілятор C для коду С. Використовуйте компілятор C ++ для коду C ++. Ні ifs, ні buts. Переписування коду С у C ++ - це зовсім інша річ, і може - а може і не бути - варто часу та ризиків.
Toby Speight

2
Я хотів би додати до поради @TobySpeight: Якщо вам потрібно використовувати C-код у проекті C ++, зазвичай ви можете зібрати код C як C (наприклад gcc -c c_code.c), код C ++ як C ++ (наприклад g++ -c cpp_code.cpp), а потім зв’язати їх разом (наприклад, gcc c_code.o cpp_code.oабо навпаки, залежно від залежностей проекту). Тепер не повинно бути причин позбавляти себе будь-яких приємних рис будь-якої мови ...
аутизм

1
@ user877329 Це більш розумна альтернатива кропітку додавання ролях до коду, що знижує розбірливість коду, лише задля того, щоб він був "C ++ сумісним".
аутист

1
Мабуть, головна перевага в цьому контексті полягає в тому, що C дозволяє писати p = malloc(sizeof(*p));, що не потрібно змінювати в першу чергу, якщо pзміниться на інше ім'я типу. Пропонована "перевага" кастингу полягає в тому, що ви отримуєте помилку компіляції, якщо pце неправильний тип, але ще краще, якщо це просто працює.
Пітер Кордес

1
Я хотів би зазначити, що написання на C може бути необхідним, коли орієнтуватися на платформи, на яких не вистачає належних компіляторів C ++. Винятки та шаблони - це функції, які, як правило, допомагають c ++ генерувати менший та / або більш ефективний код, тоді як поліморфізм часу в C ++ здебільшого еквівалентний C.
user7860670

15

Ні, ви не піддаєте результат malloc().

Взагалі ви не кидаєте на або з нихvoid * .

Типовою причиною цього не є те, що невдача #include <stdlib.h>може пройти непомітно. Це вже давно не проблема, оскільки C99 зробив неявні декларації функції незаконними, тому якщо ваш компілятор відповідає щонайменше C99, ви отримаєте діагностичне повідомлення.

Але є набагато вагоміша причина не вводити зайві вказівки:

У C введення покажчика майже завжди є помилкою . Це пояснюється наступним правилом ( §6.5 p7 у N1570, останній проект для C11):

Об'єкт має збережене значення, доступ до якого має лише вираз lvalue, який має один з таких типів:
- тип, сумісний з ефективним типом об'єкта,
- кваліфікована версія типу, сумісна з ефективним типом об'єкта,
- тип, який є типовим або непідписаним типом, що відповідає ефективному типу об'єкта,
- тип, який є типовим або непідписаним типом, що відповідає кваліфікованій версії ефективного типу об'єкта,
- сукупний або об'єднаний тип, що включає в себе один вищезазначених типів серед його членів (у тому числі, рекурсивно, член субагрегату або вміщеного союзу), або
- тип символів.

Це також відомо як правило суворого псевдоніму . Отже, наступний код - це невизначена поведінка :

long x = 5;
double *p = (double *)&x;
double y = *p;

І, що іноді дивно, так само:

struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;

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

тл; д-р

Коротше кажучи: оскільки у випадку C у будь-якому появі вказівника повинно виникати червоний прапор для коду, який потребує особливої ​​уваги, ви ніколи не повинні писати зайвих вказівників.


Бічні нотатки:

  • Є випадки , коли ви на самому справі потрібно приведення до void *, наприклад , якщо ви хочете надрукувати покажчик:

    int x = 5;
    printf("%p\n", (void *)&x);

    Тут необхідний склад, тому що printf()це варіативна функція, тому неявні перетворення не працюють.

  • У С ++ ситуація інша. Типи вказівних кастингу є дещо поширеними (і правильними) при роботі з об'єктами похідних класів. Тому має сенс, що в C ++ перетворення на та з неvoid * є неявними. C ++ має цілий набір різних ароматів лиття.


1
У своїх прикладах ви уникаєте пустоти *. є різниця між кинутими від подвійного * до int * і навпаки. malloc повертає pointel, вирівняний за найбільшим стандартним типом, тому не існує порушених правил псевдонімування, навіть якщо хтось кидає формувати цей вирівняний покажчик на інший тип.
P__J__

Aliasing взагалі не має нічого спільного з вирівнюванням, а для решти коментарів - ви, очевидно, не зрозуміли.

@PeterJ: на всякий випадок, справа полягає в тому, щоб уникнути зайвого відтворення покажчика, тому він не схожий на фрагмент коду, на який потрібно звернути особливу увагу.

Суворий виклик виправлення насправді не має нічого спільного з недійсними покажчиками. Щоб отримати помилки, спричинені суворими порушеннями дозволу, вам слід скасувати посилання на вказані дані. А оскільки ви не можете скасувати посилання на недійсний вказівник, такі помилки за визначенням не пов'язані з покажчиком недійсності, а з чимось іншим.
Лундін

Швидше, вам доведеться скласти правило забороняти всі вказівки. Але тоді як би ви писали такі речі, як підпрограми серіалізації та програмне забезпечення, пов'язані з обладнанням? Речі, які є силою С. Такі касти чудово, якщо ти знаєш, що робиш.
Лундін

15

Я вважаю за краще робити акторський склад, але не вручну. Моїм улюбленим є використання g_newі g_new0макросів із глібу. Якщо гліб не використовується, я б додав подібні макроси. Ці макроси зменшують дублювання коду без шкоди для безпеки типу. Якщо ви неправильно введете тип, ви отримаєте неявний заголовок між недійсними покажчиками, що може спричинити попередження (помилка в C ++). Якщо ви забудете включити заголовок, який визначає, g_newі g_new0ви отримаєте помилку. g_newі g_new0обидва беруть однакові аргументи, на відміну від того, mallocщо бере менше аргументів, ніж calloc. Просто додайте, 0щоб отримати нульову ініціалізовану пам'ять. Код можна скласти за допомогою компілятора C ++ без змін.


12

Кастинг призначений лише для C ++, а не для C. Якщо ви використовуєте компілятор C ++, краще змінити його на компілятор C.


9

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


11
" Це не обов'язково - хоча ви повинні це зробити " - я думаю, що тут є суперечність!
Toby Speight

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

9

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

Однак, якщо ви хочете, щоб той самий код працював ідеально сумісним на платформі C ++, яка не підтримує неявне перетворення, вам потрібно зробити набір даних, тому все залежить від зручності використання.


9
  1. Як було сказано в іншому, він потрібен не для C, а для C ++.

  2. Включаючи команду, можливо, програма C або функція можуть компілювати як C ++.

  3. У C це непотрібно, оскільки void * автоматично та безпечно просувається до будь-якого іншого типу вказівника.

  4. Але якщо ви будете робити це, ви можете приховати помилку, якщо ви забули включити stdlib.h . Це може спричинити збої (або, що ще гірше, не спричинити збої до подальшого шляху в якійсь абсолютно іншій частині коду).

    Оскільки stdlib.h містить прототип для malloc, знайдено. За відсутності прототипу для malloc, стандарт вимагає, щоб компілятор C припускав, що malloc повертає int. Якщо немає введення, видається попередження, коли це ціле число призначено покажчику; однак, при ролях це попередження не видається, приховуючи помилку.


7

Лиття малалоку непотрібне в С, але обов'язкове в С ++.

Кастинг в C не потрібний через:

  • void * автоматично та безпечно просувається до будь-якого іншого типу вказівника у випадку С.
  • Він може приховати помилку, якщо ви забули включити <stdlib.h> . Це може спричинити збої.
  • Якщо вказівники та цілі числа мають різний розмір, ви приховуєте попередження шляхом кастингу і можуть втратити біти повернутої адреси.
  • Якщо тип вказівника змінено при його оголошенні, можливо, знадобиться також змінити всі рядки, де mallocвикликається і вводиться.

З іншого боку, кастинг може збільшити портативність вашої програми. тобто це дозволяє програмі або функції C збирати як C ++.


0

Для мене, взяти додому і висновок тут полягає в тому, що кастинг mallocна C абсолютно не потрібен, але якщо ви все-таки киньте, це не буде впливати mallocякmalloc все-таки виділить вам потрібний благословенний простір пам'яті. Ще один прийом додому - це причина чи одна з причин, коли люди роблять кастинг, і це полягає в тому, щоб вони могли скласти ту саму програму або на C, або на C ++.

Можуть бути й інші причини, але інші причини, майже напевно, рано чи пізно приведуть вас до серйозних проблем.


0

Ви можете, але не потрібно робити це в C. Ви повинні подати, якщо цей код складений як C ++.

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