Перевірка, виділено вказівнику пам’ять чи ні


74

Чи можемо ми перевірити, чи вказівник, переданий функції, виділений пам'яттю чи ні в C?

Я написав власну функцію в C, яка приймає покажчик символів - buf [вказівник на буфер] та size - buf_siz [розмір буфера]. Насправді перед викликом цієї функції користувач повинен створити буфер і виділити йому пам'ять buf_siz.

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

EDIT1: Здається, немає стандартної бібліотеки, щоб перевірити це .. але чи є якийсь брудний хак, щоб перевірити це .. ??

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

Висновок: Отже, неможливо перевірити, чи певний вказівник виділений пам'яттю чи ні в межах функції


3
Я насправді так не думаю, але я не відчуваю себе достатньо впевнено, щоб написати відповідь.
Ібрагім,

Немає можливості перевірити, якщо ви не використовуєте диспетчер пам'яті або не прокручуєте власну.
Michael Foukarakis

Якщо це покажчик символів, ми можемо зробити strlen () або sizeof () і перевірити, скільки пам'яті виділено (звичайно, якщо рядок закінчується NULL). Для інших типів я не впевнений, чи є якийсь спосіб. !!
mk ..

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

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

Відповіді:


33

Ви не можете перевірити, за винятком деяких хаків, специфічних для реалізації.

Покажчики не мають з собою жодної інформації, крім того, куди вони вказують. Найкраще, що ви можете зробити, це сказати "Я знаю, як ця конкретна версія компілятора розподіляє пам'ять, тому я розмежую пам'ять, переміщу вказівник назад на 4 байти, перевіряю розмір, переконуюсь, що він збігається ..." тощо. Ви не можете зробити це стандартним способом, оскільки розподіл пам'яті визначений реалізацією. Не кажучи вже про те, що вони, можливо, взагалі не розподіляли його динамічно.

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


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

1
Гаразд, як щодо цього? Оскільки це C, то, ймовірно, використовувався клієнт, mallocякий повертає NULLпокажчик, якщо він не зміг розподілити пам'ять. Отже ... в mallocми довіряємо?
Jacob

Клієнт повинен переконатися, що malloc працював до виклику функції, якщо це саме те, що ви говорите.
GManNickG

@jacob - Я знаю, що ми можемо перевірити на malloc ... але якщо клієнт забуде зробити malloc, це призведе до помилки сегментації .. і я хочу цього уникнути.
codingfreak

7
Так. Кінцевий висновок - ваша функція повинна робити одне і одне. Уявіть собі накладні витрати, якщо кожна функція переконалася, що пам’ять, до якої вона отримувала доступ з параметрів, була дійсною. Просто нехай ваша функція робить те, що вона повинна робити.
GManNickG

10

Наведений нижче код - це те, що я використовував один раз для перевірки того, чи намагається якийсь вказівник отримати доступ до нелегальної пам'яті. Механізм полягає в індукції SIGSEGV. Раніше сигнал SEGV був перенаправлений до приватної функції, яка використовує longjmp для повернення до програми. Це свого роду хак, але це працює.

Код можна вдосконалити (використовуйте "sigaction" замість "signal" тощо), але це лише для того, щоб дати уявлення. Також він переноситься до інших версій Unix, для Windows я не впевнений. Зверніть увагу, що сигнал SIGSEGV не повинен використовуватися десь у вашій програмі.


3
@Saco i = malloc(1);є дійсним кодом C та кращим i = (int*) malloc(1);. Можливо, ви думаєте про іншу мову.
chux

Примітка під POSIX, setjmp()і, longjmp()можливо, її слід замінити на sigsetjmp()та siglongjmp(). Дивіться stackoverflow.com/questions/20755260/…
Ендрю Генле,

ІМХО, немає гарантії, що недійсний доступ до пам'яті спричинить SEGV - ви можете c = *(char *)(x);пройти нормально, навіть якщо xне вказує на виділену область. SEGVспрацьовує лише в тому випадку, якщо вказівник вказує всередині сегмента пам'яті, який не є доступним, але сегменти мають розмір у кілька кБ, отже, якщо виділити 4 байти 10, зміни полягають на тому, що адреса мему 20, незважаючи на поза виділеної області, все ще знаходиться в тому самому сегменті як адреса 10, отже, хоча вона не виділена, ви все одно зможете отримати доступ до адреси 20без SEGV.
Michael Beer

Саме тому ви завжди повинні встановлювати невикористовувані покажчики NULL, тому що це значення буде гарантовано викликати SEGV , якщо ви намагаєтеся разименовиваете ... Це не гарантуються для будь-якого іншого адреси пам'яті.
Майкл Бір,

9

Для рішення, яке відповідає певній платформі, вас може зацікавити функція Win32 IsBadReadPtr(та інші подібні). Ця функція зможе (майже) передбачити, чи не отримаєте ви помилку сегментації під час читання з певної частини пам'яті.

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


@Greg - Вибачте, що мене не дуже цікавлять функції WIN32 .. якщо можливо, добре працюючий брудний хак - це нормально, оскільки стандартної функції C НЕМАЄ
codingfreak

2
Добре, ви не вказали , яку платформу ви будете зацікавлені в. Визначенні платформи і компілятора може отримати вам конкретнішу відповідь.
Грег Хьюгілл,


7

Я завжди ініціалізую покажчики до нульового значення. Тому, коли я виділю пам'ять, вона зміниться. Коли я перевіряю, чи виділено пам’ять, я це роблю pointer != NULL. Коли я звільняю пам'ять, я також встановлюю покажчик на нуль. Я не можу придумати жодного способу визначити, чи достатньо пам'яті.

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


@Yelonek .. Я згоден з тобою, але я справді хочу знати, чи є можливість перевірити ....
codingfreak

1
Я теж, але (особливо в бібліотеках) буває ***.
Sellorio

7

Одного разу я використав брудний хак на своєму 64-бітному Solaris. У 64-бітному режимі купа починається з 0x1 0000 0000. Порівнюючи покажчик, я міг визначити, чи був це покажчиком у сегменті даних або коду p < (void*)0x100000000, покажчиком у купі p > (void*)0x100000000або покажчиком у відображеній в пам'яті області (intptr_t)p < 0(mmap повертає адреси зверху адресної області). Це дозволило моїй програмі зберігати виділені та відображені на пам’ять покажчики на одній карті, а модуль карти звільнити правильні вказівники.

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


4

Ні, загалом це неможливо зробити.

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

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


Хоча це загалом найкраща відповідь і, в основному, правильна, я б сказав: Доклавши достатньо зусиль, ви можете застосувати власний власний завантажувач для відстеження всіх виділень пам'яті - або скористатися існуючим інструментом, подібним valgrind;)
Майкл Бір,

3

Ви можете спробувати перевірити, чи вказує вказівник на стек виділеної пам'яті. Це загалом вам не допоможе, оскільки виділений буфер може бути малим або покажчик вказує на якийсь розділ глобальної пам'яті (.bss, .const, ...).

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


Так ... Якщо я напишу весь додаток, я зможу це зробити .. Але для того, щоб використовувати функцію для перевірки, речі можуть бути складними ..?
codingfreak

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

Розрізнення покажчиків з точки зору того, що вони були розподілені в купі або стеку, насправді тут не допомагає - а що, char copy[255] = {0}; snprintf(copy, sizeof(copy), "%n: %s\n", error_code, error_msg); copy[sizeof(copy) -1] = 0; write(log_fd, copy, strnlen(copy) + 1); copy[0] = 0; якщо snprintfб виконував дивні перевірки, як ви пропонували, snprintfпомилково вважав copyби недійсним покажчиком ...
Майкл Бір,

3

Я знаю , що це старе питання, але майже все можливо в C. Є кілька хака рішення вже тут, але дійсний спосіб визначення , якщо пам'ять була правильно розподілена, щоб використовувати оракул зайняти місце malloc, calloc, realloc, і free. Це те саме, що тестові фреймворки (наприклад, cmocka) можуть виявляти проблеми з пам’яттю (помилки сегментів, не звільняючи пам’ять тощо). Ви можете вести список адрес пам'яті, виділених у міру їх виділення, і просто перевірити цей список, коли користувач хоче використовувати вашу функцію. Я застосував щось дуже подібне для власного тестування. Приклад коду:

Ви б мати аналогічні функції для calloc, reallocі free, кожен обгортку з префіксом __wrap_. Реальний mallocдоступний завдяки використанню __real_malloc(аналогічно для інших функцій, які ви обгортаєте). Щоразу, коли ви хочете перевірити, чи справді виділено пам’ять, просто перейдіть по зв’язаному memory_refсписку та знайдіть адресу пам’яті. Якщо ви знайдете його, і він досить великий, ви точно знаєте, що адреса пам'яті не призведе до збою програми; в іншому випадку поверніть помилку. У файлі заголовка, який використовує ваша програма, ви додасте такі рядки:

Мої потреби були досить простими, тому я реалізував дуже базову реалізацію, але ви можете собі уявити, як це можна було б розширити, щоб мати кращу систему відстеження (наприклад, створити, structщо відстежує місце розташування пам'яті на додаток до розміру). Тоді ви просто компілюєте код за допомогою

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


2

Я не знаю, як це зробити за допомогою бібліотечного дзвінка, але на Linux ви можете подивитися /proc/<pid>/numa_maps. У ньому будуть показані всі розділи пам'яті, а в третьому стовпці буде написано "купа" або "стек". Ви можете подивитися значення вихідного вказівника, щоб побачити, де воно вирівнюється.

Приклад:

Отже, покажчики, які перевищують 0x01167000, але нижче 0x7f39904d2000, знаходяться в купі.


1

Ви не можете зв’язати нічого з наявного у стандартній C. Навіть якби ваш конкретний компілятор надав функцію для цього, це все одно буде поганою ідеєю. Ось приклад, чому:


@Mark - У коді, який ви призначаєте str як масив розміром COUNT .., отже, у 'YourFunc' я все ще можу виконувати такі операції, як strcpy, у розмірі buf_size. Але якщо str - це просто вказівник на символ, тоді спроба виконати будь-яку операцію strcpy розміром buf_size призведе до 'Помилки сегментації'
Помилки

2
Це ДУЖЕ ДУЖЕ неправильно, codingfreak. Помилка сегментації трапляється, якщо 'str' є покажчиком символу, що вказує на пам'ять, до якої ви не маєте доступу. Це не відбувається, тому що 'str' - це вказівник на символ, це відбувається тому, що ви просите програму зробити те, чого заборонено робити.
ґнуд

1

Як сказали всі інші, не існує стандартного способу зробити це.

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


@Jonathan - Що ви маєте на увазі під сторонніми бібліотеками - ?? Я просто використовую стандартні бібліотеки та ISO C99. Але я просто спробую книгу, яку ви порекомендували.
codingfreak

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

1

загалом користувачі lib відповідають за перевірку та перевірку вводу. Ви можете побачити ASSERT або щось у коді lib, і вони використовуються лише для налагодження. це стандартний спосіб написання C / C ++. в той час як дуже багато кодерів люблять робити таку перевірку та верфіку у своєму lib-коді дуже ретельно. справді "БАД" звички. Як зазначено в IOP / IOD, інтерфейси lib повинні бути контрактами і чітко вказувати, що буде робити lib, а що ні, і що повинен робити lib користувач, а що не потрібно.


1

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

переконайтеся, що він виділяє пам'ять, використовуючи вашу функцію, таку як

якщо цей struct_var містить пам'ять, яка динамічно виділяється, наприклад,

якщо визначення struct_type було

то у вашій функції init_struct_type () зробіть це,

Таким чином, якщо він не виділить рядок temp-> зі значенням, він залишатиметься НУЛОМ. Ви можете перевірити функції, які використовують цю структуру, якщо рядок NULL чи ні.

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


1

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

Я вирішив це досить просто - в частині ініціалізації main (), після того, як я заявив LIST *ptr, я просто помістив це ptr=NULL. Подобається це -

Отже, коли розподіл не вдається або ваш вказівник взагалі не виділяється, це буде NULL. ТАК ви можете просто перевірити це за допомогою if.

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

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


0

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


@Chuck, якщо немає стандартної функції бібліотеки для перевірки, чи є якийсь інший вихід ..?
codingfreak

0

Неініціалізований вказівник - саме це - неініціалізований. Він може вказувати на що-небудь або просто бути недійсною адресою (тобто такою, яка не відображена у фізичній або віртуальній пам'яті).

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

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


Можливо, ви захочете перевірити своє перше речення: "Ініціалізований покажчик - це саме той - неініціалізований".
Chris Lutz,

0

Індикатор покажчика, відстежує та перевіряє дійсність покажчика

використання:

створити пам’ять int * ptr = malloc (sizeof (int) * 10);

додати адресу вказівника до трекера Ptr (& ptr);

перевірити відсутність покажчиків PtrCheck ();

і звільнити всі трекери в кінці коду

PtrFree ();


-1

У комп’ютерах майже ніколи не буває «ніколи». Крос-платформа - це далеко не передбачуване. Після 25 років я працював над сотнями проектів, що передбачали крос-платформу, і це так і не здійснилося.

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

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

Занадто просто. Гей, це правильно c ++? Ні. Чи важливо правильно? За 25 років я побачив набагато більшу оцінку правильності. Ну, давайте скажемо це так: якщо ви зломщите, ви не займаєтесь реальним програмуванням, ви, мабуть, просто відновлюєте те, що вже було зроблено.

Наскільки це цікаво?


1
Початкове запитання стосувалось C, а не C ++, не згадувало і не передбачало змінних у стеку, а також не цікаве / нове / унікальне.
Олексій Фрунзе

Крім того, mallocфункції-подібні не обов'язково виконують дію, яка закінчиться розширенням купи. С ++ має абсолютно новий спосіб розподілу пам’яті, і всі знають, що використання заздалегідь визначених функцій С - це не гарна ідея.
Imobilis

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