malloc для структури та вказівника в C


84

Припустимо, я хочу визначити структуру, що представляє довжину вектора та його значення, як:

Тепер, припустимо, я хочу визначити вектор y і виділити для нього пам’ять.

Мій пошук в Інтернеті показує, що я повинен виділити пам’ять для x окремо.

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

Заздалегідь спасибі.


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

14
@unwind, можливо, вони старі програмісти на C ++, які
переходять

@unwind При використанні компілятора nvicc від Nvidia на коді C, якщо я не видаю результат malloc, він видає помилку.
Nubcake

@Nubcake Згідно з цим посиланням, це може бути тому, що nvcc запускає базовий компілятор у режимі C ++, оскільки їх інтерфейс CUDA є C ++. У C ви не отримаєте помилок за це. У C ++ void *не перетворюється автоматично на інші покажчики, і необхідний привід (або, malloc()звичайно , просто не використовуйте в C ++).
розслабтесь

@unwind Так, я пізніше дізнався про це :) Просто хотів сформулювати ситуацію, коли, якщо ви не приведете результат, це призведе до помилки.
Набкейк,

Відповіді:


156

Ні, ви не виділяєте пам'ять y->xдвічі.

Натомість ви виділяєте пам’ять для структури (яка включає вказівник) плюс щось, на що вказує цей вказівник.

Подумайте про це так:

Отже, вам насправді потрібні два розподіли ( 1і 2), щоб зберігати все.

Крім того, типом має бути, struct Vector *yоскільки це вказівник, і ніколи не слід відкидати значення, що повертається з mallocC, оскільки воно може приховувати певні проблеми, які ви не хочете приховувати - C чудово здатний неявно перетворити void*повернене значення в будь-який інший покажчик.

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

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


28
Тремтіти від мого мистецького вміння ASCII :-)
paxdiablo

Дуже дякую. Дуже корисно.
Pouya

1
Якщо if (retval == NULL), retval повинен бути retVal
Legion Daeth

5

Перший раз, ви виділити пам'ять для Vector, що означає , що змінні x, n.

Однак x поки не вказує ні на що корисне .

Отже, тому необхідний і другий розподіл .


3

Кілька пунктів

struct Vector y = (struct Vector*)malloc(sizeof(struct Vector)); неправильно

це повинно бути, struct Vector *y = (struct Vector*)malloc(sizeof(struct Vector));оскільки yмає покажчик на struct Vector.

Перший malloc()лише виділяє пам’ять, достатню для зберігання структури Vector (яка вказує на double + int)

2-й malloc()фактично виділяють пам'ять для зберігання 10 подвійних.


2

Насправді ви можете зробити це в одному malloc, виділивши для вектора та масиву одночасно. Наприклад:

Це виділяє Vector 'y', а потім змушує y-> x вказувати на додаткові виділені дані відразу після структури Vector (але в тому самому блоці пам'яті).

Якщо потрібна зміна розміру вектора, вам слід зробити це з двома розподілами, як рекомендовано. Тоді можна буде змінити розмір внутрішнього масиву y-> x, зберігаючи незмінною векторну структуру 'y'.


Чому ви набрали спеціальну форму «char» саме для позначення? Чому б не (подвійний *) y + sizeof (struct Vector)?
Вішну Прасат

sizeof повертає розмір структури в байтах, а арифметичний оператор покажчика '+' додасть до покажчика 'y' кратні розміру ( y). Якби ми зробили, як ви робили вище, y було б збільшено на sizeof (double) * sizeof (struct), що занадто. Прив’язка y до char дозволяє нам збільшувати y за розміром (char) * sizeof (struct) = 1 * sizeof (struct)
PQuinn

2

В принципі, ви все робите правильно. Для того, що ви хочете, вам потрібно два malloc()с.

Лише кілька коментарів:

має бути

У першому рядку ви виділяєте пам’ять для об’єкта Vector. malloc()повертає покажчик на виділену пам'ять, тому y повинен бути векторним вказівником. У другому рядку ви виділяєте пам'ять для масиву з 10 подвійних.

У C вам не потрібні явні закиди, а sizeof *yзамість цього sizeof(struct Vector)краще писати для безпеки типу, а крім того, це економить на друкуванні.

Ви можете переставити свою структуру і зробити одиницю malloc()приблизно так:


"економить на друкуванні" ніколи не є вагомим аргументом для прийняття рішень щодо програмування. Справжньою причиною, яку ви б взяли * y, є міркування безпеки, гарантуючи, що ви виділите стільки місця, скільки потрібно для відповідної змінної.
Лундін,

@Lundin Я оновив свою відповідь, але для мене аргумент "безпека типу" майже у тій самій лізі, що і написанняif(NULL == foo)
Вернсі,

Ви є той, хто проповідує, щоб використовувати sizeof *y. Якщо ви робите це лише для того, щоб набирати на 5 літер менше (sizeof * y проти sizeof (Vector)), не використовуючи жодного іншого аргументу, то це повинно бути тому, що ви вважаєте завдання набору 5 букв на клавіатурі головною перешкодою. А якщо так, то, можливо, розглянемо інший вибір кар’єри, оскільки програмування передбачає багато введення тексту на клавіатурі ...
Лундін,

@Lundin Writing sizeof *yдопомагає боротися з такими помилками, як написання, sizeof(Vector)коли ви мали на увазі sizeof(Matrix). Як часто ви робите такі помилки? Як швидко ви їх знаходите та виправляєте? Я згоден з тим, що це підвищує безпеку типу, і я пишу sizeof *yу своєму коді, але це майже так само параноїчно, як написання, if(NULL == foo)щоб запобігти друкарським помилкам ==.
Вернсі

1
@ShmuelKamensky Вони функціонально еквівалентні. yє вказівником на struct Vectorте, що sizeof *yозначає "розмір того, на що вказує y", так sizeof struct Vector.
Вернсі

1

Коли ви виділяєте пам'ять, struct Vectorви просто виділяєте пам'ять для вказівника x, тобто для простору, де буде розміщено його значення, яке містить адресу. Таким чином, ви не виділяєте пам'ять для блоку, на який y.xбуде посилатися.


1

Спочатку malloc виділяє пам'ять для struct, включаючи пам'ять для x (вказівник на double). Другий malloc виділяє пам'ять для подвійного значення, до якого x вказує.


0

Коли ти malloc(sizeof(struct_name)) він автоматично виділяє пам'ять для повного розміру структури, вам не потрібно malloc кожен елемент всередині.

Використовуйте -fsanitize=addressпрапор, щоб перевірити, як ви використовували пам’ять програми.


Неправильно. x - це просто вказівник, вам потрібно виділити пам'ять для значення, на яке вказує x. Прочитайте іншу відповідь, щоб отримати докладнішу інформацію. (напр .: stackoverflow.com/a/14768280/5441253 )
Максим Ашуров,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.