Зокрема, що небезпечного у відливанні результату malloc?


86

Тепер, перш ніж люди починають позначати це дублем, я прочитав усе наступне, жодне з яких не дає відповіді, яку я шукаю:

  1. C Поширені запитання: Що поганого в литті поверненого значення malloc?
  2. ТАК: Чи слід явно відтворювати повернене значення malloc ()?
  3. ТАК: Немає необхідності вказівників на C
  4. ТАК: Чи я кидаю результат malloc?

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

Тепер дано такий код:

#include <string.h>
#include <stdio.h>
// #include <stdlib.h>

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

Компіляція вищезазначеного коду з gcc 4.2, з приводом і без, дає однакові попередження, і програма виконує належним чином і забезпечує однакові результати в обох випадках.

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
anon@anon:~/$ ./nostdlib_malloc 
hello

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

Редагувати Я натрапив на два добре написані аргументи щодо цього питання:

  1. На користь кастингу: Повідомлення щодо CERT: негайно передайте результат виклику функції виділення пам'яті у покажчик на виділений тип
  2. Проти кастингу (помилка 404 станом на 14.02.2012: скористайтеся копією Інтернет-архіву Wayback Machine від 27.01.2010. {2016-03-18: "Сторінку неможливо просканувати або відобразити через robots.txt."})

6
voidвказівники лиття дозволяють компілювати код як C ++; дехто каже, що це особливість, я б сказав, що це помилка;)
Крістоф

1
також прочитайте коментарі до першого з ваших посилань, оскільки там описується, що ви повинні робити замість кастингу: securecoding.cert.org/confluence/display/seccode/…
Крістоф

3
Я прийму пораду CERTs щодо включення акторського складу. Крім того, я не забуду включити stdlib.h, ніколи. :)
Абхінав,

1
Ось SO-приклад помилки виконання компіляції через mallocповернене значення кастингу: лиття до int*64-бітної арки.
John_West

1
це питання позначено CНЕ C++(вони дві різні мови) Таким чином , будь-яке обговорення (як і в деяких з відповідей) НЕ має відношення до цього питання.
user3629249

Відповіді:


66

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

Отже, помилка на вашому боці не в акторському складі, а в тому, що ви забули включити stdlib.h. Компілятори можуть припустити, що mallocце функція, яка повертається int, тому перетворює void*вказівник, фактично повернутий mallocдо, intа потім у ваш тип покажчика завдяки явному приведенню. На деяких платформах intі вказівники можуть займати різну кількість байтів, тому перетворення типів може призвести до пошкодження даних.

На щастя, сучасні компілятори дають попередження, які вказують на вашу фактичну помилку. Перегляньте gccвихідні дані, які ви надали: це попереджає вас, що неявне оголошення ( int malloc(int)) несумісне із вбудованим malloc. Так, gccздається, знає mallocнавіть без stdlib.h.

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

if (0 == my_var)

замість

if (my_var == 0)

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

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


2
Здавалося б, оскільки компілятор попереджає про несумісну неявну декларацію, то це не проблема, якщо ви звертаєте увагу на попередження компілятора.
Роберт С. Барнс,

4
@ Роберт: так, враховуючи певні припущення щодо компілятора. Коли люди дають поради, як найкраще писати C загалом , вони не можуть припустити, що особа, яка отримує пораду, використовує останню версію gcc.
Steve Jessop

4
О, і відповідь на друге запитання полягає в тому, що абонент містить код, щоб забрати повернене значення (яке, на його думку, є int), і перетворити його в T *. Виклик просто записує повернене значення (як порожнечу *) і повертає. Отже, залежно від домовленості про виклик: int повертає і void * повертається може бути, а може і не бути в "одному місці" (реєстр або слот стека); int і void * може мати або не мати однаковий розмір; конвертація між ними може бути, а може і не бути. Тож це може "просто спрацювати", або значення може бути пошкоджене (можливо, втрачено кілька бітів), або абонент може прийняти зовсім неправильне значення.
Steve Jessop

1
@ RobertS.Barnes запізнюється до учасника, але: Повернене значення, як правило, не є частиною підпису функції, навіть у C ++. Посилання просто генерує перехід до символу, ось і все.
Пітер - Відновити Моніку

3
Ви можете отримати непередбачувану помилку під час використання, не використовуючи stdlib.h . Це правда, але не враховуючи stdlib.hце вже є помилкою само по собі, навіть якщо ви отримуєте лише попередження "неявної декларації".
Jabberwocky

45

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

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

Імена типів належать до оголошень. Наскільки це можливо, імена типів повинні бути обмежені деклараціями і лише деклараціями.

З цієї точки зору, цей біт коду поганий

int *p;
...
p = (int*) malloc(n * sizeof(int));

і це набагато краще

int *p;
...
p = malloc(n * sizeof *p);

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


Fwiw, я думаю, що це більш-менш та сама причина, що і ця: stackoverflow.com/questions/953112/…, але орієнтована на незалежність від типу, а не на DIY. Звичайно, перше випливає з другого (або навпаки), тому, принаймні, це іноді згадується . :)
розслабтесь

5
@unwind ви , швидше за все , середня DRY , а не DIY
коротенько

18

Припускається, що функції, що не мають прототипу, повертаються int.

Отже, ви транслюєте intвказівник. Якщо покажчики ширші за ints на вашій платформі, це дуже ризикована поведінка.

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

Особисто я думаю, що той факт, що вам не потрібно перекидати void *на інший тип вказівника, є особливістю в C, і вважаю код, який має бути зламаним.


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

3
У багатьох проектах код С компілюється як С ++, де вам потрібно закинути void*.
laalto

nit: " за замовчуванням непрототипні функції передбачають повернення int." - Ви маєте на увазі, що можна змінити тип повернення непрототипних функцій?
pmg

1
@laalto - Це так, але не повинно бути. C - це C, а не C ++, і його слід компілювати за допомогою компілятора C, а не компілятора C ++. Немає виправдання: GCC (один із найкращих компіляторів C) працює майже на кожній платформі, яку тільки можна уявити (і також генерує високооптимізований код). Які причини можуть виникнути у вас для компіляції C за допомогою компілятора C ++, крім лінощів та неміцних стандартів?
Chris Lutz,

3
Приклад коду , який ви можете скомпілювати і як C і C ++: #ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif. Тепер, чому ви хочете викликати malloc у статичній вбудованій функції, я насправді не знаю, але заголовки, які працюють в обох, навряд чи нечувані.
Steve Jessop

11

Якщо ви зробите це під час компіляції в 64-розрядному режимі, ваш повернутий покажчик буде усічений до 32-бітового.

РЕДАГУВАТИ: Вибачте за занадто короткий термін. Ось приклад фрагмента коду для обговорення.

основний ()
{
   char * c = (char *) malloc (2);
   printf ("% p", c);
}

Припустимо, що повернутий покажчик купи є чимось більшим, ніж те, що можна представити в int, скажімо 0xAB00000000.

Якщо malloc не прототипується для повернення вказівника, значення int, що повертається, спочатку буде в якомусь регістрі з усіма значущими бітами. Тепер компілятор каже: "гаразд, як мені перетворити та int у покажчик". Це буде або розширенням знака, або нульовим розширенням 32-бітового низького порядку, про що йому було сказано, що malloc "повертається", опускаючи прототип. Оскільки int підписаний, я думаю, що перетворення буде розширенням знака, яке в цьому випадку перетворить значення на нуль. З поверненим значенням 0xABF0000000 ви отримаєте ненульовий покажчик, який також викличе певне задоволення при спробі його розмежування.


1
Не могли б ви детально пояснити, як це могло статися?
Роберт С. Барнс,

5
Я думаю, Пітер Джоут з'ясував, що "За замовчуванням непрототипні функції, як передбачається, повертають int", включаючи stdlib.h, і sizeof (int) становить 32 біти, а sizeof (ptr) - 64.
Тест

4

Правило програмного забезпечення для багаторазового використання:

У випадку написання вбудованої функції, в якій використано malloc (), щоб зробити його багаторазовим для коду С ++, будь ласка, виконайте явний привід типу (наприклад, (char *)); інакше компілятор скаржиться.


ми сподіваємось, з (нещодавньою) включеністю оптимізації часу посилання в gcc (див. gcc.gnu.org/ml/gcc/2009-10/msg00060.html ), оголошення вбудованих функцій у файлах заголовків більше не буде необхідним
Крістоф

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

2
при написанні C ++ malloc / free - НЕ правильна методологія. Скоріше використовуйте new / delete. IE не повинно бути / nada / нульових дзвінків на malloc / free в коді С ++
user3629249

3
@ User3629249: При написанні функції , яка повинна бути придатними для використання в будь-якому коді C або C ++ коду, використовуючи malloc/ freeдля обох схильні бути краще , ніж намагатися використовувати mallocв C і newв C ++, особливо якщо структури даних розділені між C і C ++ коду, і існує ймовірність того, що об’єкт може бути створений в коді С та випущений у коді С ++ або навпаки.
supercat

3

Покажчик порожнечі в C може бути призначений будь-якому покажчику без явного приведення. Компілятор дасть попередження, але його можна багаторазово використовувати в C ++ за допомогою приведення типу malloc()до відповідного типу. При відливанні поза типом також його можна використовувати в C , оскільки C не є суворою перевіркою типу . Але C ++ - це суворо перевірка типу, тому для введення тексту malloc()в C ++ потрібно.


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