Передача динамического массива в функции в C

Я пытаюсь создать функцию, которая принимает массив в качестве аргумента, добавляет к нему значения (при необходимости увеличивая его размер) и возвращает количество элементов. Пока у меня есть:

int main(int argc, char** argv) {
    int mSize = 10;
    ent a[mSize];
    int n;
    n = addValues(a,mSize);

    for(i=0;i<n;i++) {
       //Print values from a
    }
}

int addValues(ent *a, int mSize) {
    int size = mSize;

    i = 0;

    while(....) { //Loop to add items to array
        if (i>=size-1) { 
            size = size*2;
            a = realloc(a, (size)*sizeof(ent));
        }
        //Add to array
        i++;
    }
    return i;
}

Это работает, если mSize достаточно велик, чтобы вместить все потенциальные элементы массива, но если требуется изменить размер, я получаю ошибку сегментации.

Я также пробовал:

int main(int argc, char** argv) {
    ...
    ent *a;
    ...
}

int addValues(ent *a, int mSize) {
    ...
    a = calloc(1, sizeof(ent);
    //usual loop
    ...
}

Но безрезультатно.

Я предполагаю, что это связано с тем, что когда я вызываю realloc, копия 'a' указывается в другом месте - как можно изменить это так, чтобы 'a' всегда указывал на одно и то же место?

Правильно ли я делаю это? Есть ли лучшие способы работы с динамическими структурами в C? Должен ли я использовать связанный список для решения этих проблем?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
0
10 588
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Попробуйте переделать его так, чтобы в него передавался указатель на указатель на массив, то есть ent **a. Тогда вы сможете обновить вызывающего абонента в новом местоположении массива.

Вам нужно объединить это с ответом tgamblin, чтобы получить полное решение.

Mark Ransom 04.12.2008 20:16
Ответ принят как подходящий

Основная проблема здесь в том, что вы пытаетесь использовать realloc с выделенным стеком массивом. У тебя есть:

ent a[mSize];

Это автоматическое размещение в стеке. Если вы захотите использовать для этого realloc () позже, вы должны создать массив в куче с помощью malloc (), например:

ent *a = (ent*)malloc(mSize * sizeof(ent));

Чтобы библиотека malloc (и, следовательно, realloc () и т. д.) Знала о вашем массиве. Судя по всему, вы можете спутать C99 массивы переменной длины с истинным динамические массивы, поэтому убедитесь, что вы понимаете разницу, прежде чем пытаться это исправить.

На самом деле, если вы пишете динамические массивы на C, вы должны попытаться использовать дизайн в стиле ООП, чтобы инкапсулировать информацию о ваших массивах и скрыть ее от пользователя. Вы хотите объединить информацию (например, указатель и размер) о вашем массиве в структуру, а операции (например, выделение, добавление элементов, удаление элементов, освобождение и т. д.) - в специальные функции, которые работают с вашей структурой. Итак, у вас может быть:

typedef struct dynarray {
   elt *data;
   int size;
} dynarray;

И вы можете определить некоторые функции для работы с динамическими массивами:

// malloc a dynarray and its data and returns a pointer to the dynarray    
dynarray *dynarray_create();     

// add an element to dynarray and adjust its size if necessary
void dynarray_add_elt(dynarray *arr, elt value);

// return a particular element in the dynarray
elt dynarray_get_elt(dynarray *arr, int index);

// free the dynarray and its data.
void dynarray_free(dynarray *arr);

Таким образом, пользователю не нужно точно помнить, как распределять объекты или какой размер массива в настоящее время. Надеюсь, это поможет вам начать.

Спасибо за это! Очень полезная информация - я собираюсь посмотреть, смогу ли я переработать свой код, как предлагаете вы и Хавьер. Если у меня есть несколько динамических массивов разных типов (массив ents, массив foos и т. д.), Можно ли создать единый набор методов для работы со всеми ними?

Tom 04.12.2008 20:11

Вы можете сделать это с помощью шаблонов в C++, но в C. это не лучший способ справиться с этим. Вы можете сделать это с помощью набора хорошо созданных макросов, но это не делает код самым красивым или удобным в сопровождении.

Todd Gamblin 04.12.2008 20:15

У вас может быть один набор методов для разных типов, но для этого вам нужно использовать указатели void *. Вам также необходимо знать размер каждого элемента и выполнять много приведений между void * и фактическим типом. Это может быть очень опасно и подвержено ошибкам.

Adam Rosenfield 04.12.2008 20:19

Вы можете попробовать сохранить размер элемента массива как часть структуры динамического массива. Хотя вам все равно понадобится много слепков.

user3458 04.12.2008 20:28

Вы передаете указатель массива по значению. Это означает следующее:

int main(int argc, char** argv) {
    ...
    ent *a; // This...
    ...
}

int addValues(ent *a, int mSize) {
    ...
    a = calloc(1, sizeof(ent); // ...is not the same as this
    //usual loop
    ...
}

поэтому изменение значения a в функции addValues не приводит к изменению значения a в main. Чтобы изменить значение a в main, вам нужно передать ссылку на него в addValues. В настоящий момент значение a копируется и передается в addValues. Чтобы передать ссылку на использование:

int addValues (int **a, int mSize)

и назовите это так:

int main(int argc, char** argv) {
    ...
    ent *a; // This...
    ...
    addValues (&a, mSize);
}

В addValues доступ к элементам такой:

(*a)[element]

и перераспределите массив следующим образом:

(*a) = calloc (...);

Когда я передаю свой массив таким образом, GDB показывает, что адрес массива ptr в функции - 0x0, тогда как адрес arr в main() показывает длинное шестнадцатеричное число. И я получаю ошибку сегментации, когда пытаюсь добавить элемент в arr в этой функции. 0x0 означает NULL? quora.com/…

mLstudent33 08.07.2020 23:57

это хорошая причина использовать ООП. да, вы можете выполнять ООП на C, и это даже выглядит красиво, если все сделано правильно.

в этом простом случае вам не нужны ни наследование, ни полиморфизм, только концепции инкапсуляции и методов:

  • определить структуру с длиной и указателем данных. возможно размер элемента.
  • напишите функции получения / установки, которые работают с указателями на эту структуру.
  • функция 'grow' изменяет указатель данных в структуре, но любой указатель структуры остается действительным.

Xahtep объясняет, как ваш вызывающий абонент может справиться с тем фактом, что realloc () может переместить массив в новое место. Пока вы это делаете, все будет в порядке.

realloc () может стать дорогим, если вы начнете работать с большими массивами. Вот тогда и пора задуматься об использовании других структур данных - связного списка, двоичного дерева и т. д.

В исходном коде используется обычная практика удвоения размера массива при каждом перераспределении, поэтому я не думаю, что это будет слишком дорого.

Mark Ransom 04.12.2008 20:15

В этом случае каждое перераспределение в два раза дороже предыдущего, но при условии постоянной скорости увеличения времени, необходимого для этого, потребовалось вдвое больше. Стоимость указана в неявной функции memcpy () для нового местоположения.

slim 04.12.2008 20:52

Конечно, но это постоянное амортизированное время. Средний случай добавления к вектору указателей - это запись в ptr, увеличение размера и амортизация memcpy на 1-2 пункта. Чтобы добавить к двусвязному списку, нужно выделить 1 для быстрой кучи, 1 ptr для полезной нагрузки и 4 для перекрестных ссылок. Это больше работы.

Steve Jessop 05.12.2008 06:48

Тем более, что memcpy на большинстве архитектур значительно быстрее, чем такое же количество непоследовательных записей указателей. Я бы не стал придавать особого значения утверждению, что экспоненциальные массивы работают медленно без профилирования, если элементы добавляются / удаляются только в конце.

Steve Jessop 05.12.2008 06:51

Я согласен - никогда не доверяйте никаким заявлениям об эффективности без профилирования.

slim 05.12.2008 13:37

Я продолжаю получать нулевой указатель: quora.com/… realloc в read_file () перед вызовом add_student () из main ().

mLstudent33 09.07.2020 00:13

Как указано, вы должны передать указатель на указатель, чтобы обновить значение указателя. Но я бы посоветовал изменить дизайн и избегать этой техники, в большинстве случаев ее можно и нужно избегать. Не зная, чего именно вы пытаетесь достичь, трудно предложить альтернативный дизайн, но я на 99% уверен, что это возможно по-другому. И как грустно Хавьер - думайте об объектах, и вы всегда получите лучший код.

Если вы изменили объявление переменной в основном на

ent *a = NULL;

код будет работать так, как вы предполагали, не освобождая выделенный стеком массив. Установка значения NULL работает, потому что realloc обрабатывает это так, как если бы пользователь вызвал malloc (size). Имейте в виду, что с этим изменением прототип addValue должен измениться на

int addValues(ent **a, int mSize)

и что код должен обрабатывать случай сбоя перераспределения. Например

while(....) { //Loop to add items to array
    tmp = realloc(*a, size*sizeof(ent));
    if (tmp) {
        *a = tmp;
    } else {
        // allocation failed. either free *a or keep *a and
        // return an error
    }
    //Add to array
    i++;
}

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

size = size * 2;

ненужный.

Вы действительно обязаны использовать C? Это было бы отличным применением C++ "std :: vector", который представляет собой именно массив динамического размера (легко изменяемый с помощью одного вызова, который вам не нужно писать и отлаживать самостоятельно).

Другие вопросы по теме