Как я могу выделить память и вернуть ее (через указательный параметр) в вызывающую функцию?
У меня есть код в нескольких разных функциях, который выглядит примерно так:
void someFunction (int *data) {
data = (int *) malloc (sizeof (data));
}
void useData (int *data) {
printf ("%p", data);
}
int main () {
int *data = NULL;
someFunction (data);
useData (data);
return 0;
}
someFunction ()
и useData ()
определяются в отдельных модулях (файлы *.c).
Проблема заключается в том, что, хотя malloc работает нормально, а выделенная память может использоваться в someFunction
, одна и та же память недоступна после возвращения функции.
Пример запуска программы можно увидеть здесь, с выходом, показывающим различные адреса памяти.
Может кто-нибудь объяснить мне, что я делаю неправильно здесь, и как я могу заставить этот код работать?
EDIT: Мне кажется, что мне нужно использовать двойные указатели, чтобы сделать это - как я буду делать то же самое, когда мне действительно нужно использовать двойные указатели? Так, например, данные
int **data = NULL; //used for 2D array
Должен ли я использовать тройные указатели в вызовах функций?
Ответы
Ответ 1
Вы хотите использовать указатель на указатель:
void someFunction (int **data) {
*data = malloc (sizeof (int));
}
void useData (int *data) {
printf ("%p", data);
}
int main () {
int *data = NULL;
someFunction (&data);
useData (data);
return 0;
}
Почему? Ну, вы хотите изменить указатель data
в основной функции. В C, если вы хотите изменить что-то, переданное в качестве параметра (и чтобы это изменение отображалось в версии для вызывающего абонента), вам нужно передать указатель на все, что вы хотите изменить. В этом случае это "то, что вы хотите изменить" - это указатель, поэтому для изменения этого указателя вы должны использовать указатель на указатель...
Обратите внимание, что помимо вашей основной проблемы в коде была еще одна ошибка: sizeof(data)
дает вам количество байтов, необходимых для хранения указателя (4 байта в 32-битной ОС или 8 байтов на 64- разрядной ОС), тогда как вы действительно хотите, чтобы количество байтов должно было хранить то, на что указывает указатель (a int
, т.е. 4 байта на большинстве ОС). Поскольку обычно sizeof(int *)>=sizeof(int)
, это, вероятно, не вызвало бы проблему, но это то, о чем нужно знать. Я исправил это в коде выше.
Вот некоторые полезные вопросы по указателям на указатели:
Как указатель на указатели работает на C?
Используется для нескольких уровней разметки указателя?
Ответ 2
Общая ошибка, особенно если вы переместили форму Java на C/С++
Помните, когда вы передаете указатель, он передает значение i.e копирует значение указателя. Это полезно для внесения изменений в данные, указываемые указателем, но любые изменения самого указателя являются только локальными, поскольку это копия!
Трюк заключается в том, чтобы использовать передачу указателя по ссылке, так как вы хотите изменить его i.e malloc it и т.д.
** указатель → напугает программиста noobie C;)
Ответ 3
Вам нужно передать указатель на указатель, если вы хотите изменить указатель.
т.
void someFunction (int **data) {
*data = malloc (sizeof (int)*ARRAY_SIZE);
}
изменить:
Добавлено ARRAY_SIZE, в какой-то момент вам нужно знать, сколько целых чисел вы хотите выделить.
Ответ 4
Это потому, что данные указателя передаются значением someFunction
.
int *data = NULL;
//data is passed by value here.
someFunction (data);
//the memory allocated inside someFunction is not available.
Указатель на указатель или возврат выделенного указателя разрешит проблему.
void someFunction (int **data) {
*data = (int *) malloc (sizeof (data));
}
int* someFunction (int *data) {
data = (int *) malloc (sizeof (data));
return data;
}
Ответ 5
someFunction() принимает свой параметр как int *. Поэтому, когда вы вызываете его из main(), создается копия полученного вами значения. Независимо от того, что вы изменяете внутри функции, эта копия и, следовательно, изменения не будут отображаться снаружи. Как и другие, вы можете использовать int ** для получения изменений, отраженных в данных. Другой способ сделать это - вернуть int * из someFunction().
Ответ 6
Помимо использования метода двойного указателя, если требуется только один возвращаемый параметр, необходимо переписать следующее:
int *someFunction () {
return (int *) malloc (sizeof (int *));
}
и используйте его:
int *data = someFunction ();
Ответ 7
Вот общий шаблон для выделения памяти в функции и возврата указателя через параметр:
void myAllocator (T **p, size_t count)
{
*p = malloc(sizeof **p * count);
}
...
void foo(void)
{
T *p = NULL;
myAllocator(&p, 100);
...
}
Другой метод - сделать указатель функцией возвращаемого значения (мой предпочтительный метод):
T *myAllocator (size_t count)
{
T *p = malloc(sizeof *p * count);
return p;
}
...
void foo(void)
{
T *p = myAllocator(100);
...
}
Некоторые примечания по управлению памятью:
- Лучший способ избежать проблем с управлением памятью - избежать управления памятью; не гадайте с динамической памятью, если вам это действительно не нужно.
- Не выдавайте результат malloc(), если вы не используете реализацию, предшествующую стандарту ANSI 1989 года, или вы собираетесь скомпилировать код как С++. Если вы забудете включить stdlib.h или иначе не имеете прототипа для malloc() в области видимости, то приведение возвращаемого значения приведет к сбою полезной диагностики компилятора.
- Используйте размер выделяемого объекта вместо размера типа данных (т.е.
sizeof *p
вместо sizeof (T)
); это спасет вас от изжоги, если тип данных должен измениться (скажем, от int до long или float, чтобы удвоить). Это также заставляет код читать немного лучше ИМО.
- Изолировать функции управления памятью за более высоким уровнем выделения и освобождать функции; они могут обрабатывать не только распределение, но и инициализацию и ошибки.
Ответ 8
Здесь вы пытаетесь изменить указатель, т.е. от "data == Null" до "data == 0xabcd", какую-то другую выделенную вами память. Таким образом, чтобы изменить данные, которые вам нужны, передайте адрес данных, а именно: данные.
void someFunction (int **data) {
*data = (int *) malloc (sizeof (int));
}
Ответ 9
Ответ на ваш дополнительный вопрос, который вы отредактировали:
'*' обозначает указатель на что-то. Таким образом, "**" будет указателем на указатель на что-то, "***" указатель на указатель на указатель на что-то и т.д.
Обычная интерпретация "int ** data" (если данные не являются параметром функции) будет указателем на список массивов int (например, "int a [100] [100]" ).
Итак, вам нужно сначала выделить ваши массивы int (для простоты я использую прямой вызов для malloc()):
data = (int**) malloc(arrayCount); //allocate a list of int pointers
for (int i = 0; i < arrayCount; i++) //assign a list of ints to each int pointer
data [i] = (int*) malloc(arrayElemCount);
Ответ 10
Вместо использования двойного указателя мы можем просто выделить новый указатель и просто вернуть его, не нужно передавать двойной указатель, потому что он не используется нигде в функции.
Возврат void *
, поэтому его можно использовать для любого типа размещения.
void *someFunction (size_t size) {
return malloc (size);
}
и используйте его как:
int *data = someFunction (sizeof(int));