簡體   English   中英

C - realloc中的動態數組

[英]Dynamic Array in C - realloc

在我開始之前

  1. 我搜索了“類似標題的問題”,雖然我發現了一些非常有用的信息,但我似乎無法讓它為我工作。

  2. 這與家庭作業有關。 雖然,不是實際的項目本身。 我已經完成了我只是將它從java移植到C所以我可以測試我的教授單元測試框架。

好的,動態分配的數組。 我知道如何構建它們,但不知道如何構建它們。

例如我有以下界面..

void insertVertex( vertex p1, vertex out[], int *size);

此方法采用頂點並將其存儲到out數組中。 存儲頂點后,我增加了未來調用的長度計數。

p1 - 是我要添加的頂點。

out [] - 是我需要存儲的數組(總是滿的)

length - 當前長度

頂點定義為..

typedef struct Vertex{
int x;
int y;
} Vertex;

這就是我在java中使用的..

Vertex tempOut = new Vertex[size +1];
//Code to deep copy each object over
tempOut[size] = p1;
out = tempOut;

這是我認為我可以用於...

out = realloc(out, (*size + 1) * sizeof(Vertex));
out[(*size)] = p1;

但是,我繼續收到一條錯誤消息,指出該對象未動態分配。

我發現了一個可以解決這個問題的解決方案。我沒有使用Vertex *而是轉向Vertex **並存儲指針與頂點。 但是,在切換完所有內容之后,我發現我已經看過單元測試將為我提供一個Vertex out []的事實,即必須存儲所有內容。

我也試過以下沒有運氣。

Vertex* temp = (Vertex *)malloc((*size + 1) * sizeof(Vertex));
for(int i = 0; i < (*size); i++)
{
temp[i] = out[i];
}
out = temp;

但是,無論我做什么,當我測試這兩個后返回的數組都沒有改變。

任何幫助表示贊賞。

更新 - 請求的信息

out - 被定義為Vertex數組(Vertex out [])

它最初是用我的多邊形中的頂點數構建的。 例如。

out =(Vertex *)malloc(vertexInPolygon * sizeof(Vertex))

其中vertexInPolygon是多邊形中頂點數的整數。

長度是應該是大小的拼寫錯誤。

Size是一個整數指針

int *size = 0;

每次頂點在剪切平面中時,我們將它添加到頂點數組並將大小增加一。

更新

謝謝大家的幫助。 為了更好地解釋自己,我想出了一個簡短的程序來展示我正在嘗試做的事情。

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

typedef struct Vertex {
    int x, y;
} Vertex;

void addPointerToArray(Vertex v1, Vertex out[], int *size);

void addPointerToArray(Vertex v1, Vertex out[], int *size)
{
    int newSize = *size;
    newSize++;

    out = realloc(out, newSize * sizeof(Vertex));
    out[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

int main (int argc, const char * argv[])
{
    //  This would normally be provided by the polygon
    int *size = malloc(sizeof(int)); *size = 3;

    //  Build and add initial vertex
    Vertex *out = (Vertex *)malloc((*size) * sizeof(Vertex));
    Vertex v1; v1.x = 1; v1.y =1;
    Vertex v2; v2.x = 2; v2.y =2;
    Vertex v3; v3.x = 3; v3.y =3;

    out[0] = v1;
    out[1] = v2;
    out[2] = v3;

    //  Add vertex
    //  This should add the vertex to the last position of out
    //  Should also increase the size by 1;
    Vertex vertexToAdd; vertexToAdd.x = 9; vertexToAdd.y = 9;
    addPointerToArray(vertexToAdd, out, size);

    for(int i =0; i < (*size); i++)
    {
        printf("Vertx: (%i, %i) Location: %i\n", out[i].x, out[i].y, i);
    }

}

一個長期問題是您沒有從addPointerToArray()函數返回更新的數組指針:

void addPointerToArray(Vertex v1, Vertex out[], int *size)
{
    int newSize = *size;
    newSize++;

    out = realloc(out, newSize * sizeof(Vertex));
    out[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

重新分配空間時,它可以移動到新位置,因此realloc()的返回值不必與輸入指針相同。 在添加到數組時沒有其他內存分配的情況下這可能會起作用,因為realloc()會在有空間的情況下擴展現有分配,但是一旦你在讀取頂點時開始分配其他數據,它將會失敗。 。 有幾種方法可以解決這個問題:

Vertex *addPointerToArray(Vertex v1, Vertex out[], int *size)
{
    int newSize = *size;
    newSize++;

    out = realloc(out, newSize * sizeof(Vertex));
    out[(*size)] = v1;

    //  Update Size
    *size = newSize;
    return out;
}

和調用:

out = addPointerToArray(vertexToAdd, out, size);

或者,您可以傳入指向數組的指針:

void addPointerToArray(Vertex v1, Vertex **out, int *size)
{
    int newSize = *size;
    newSize++;

    *out = realloc(*out, newSize * sizeof(Vertex));
    (*out)[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

和調用:

out = addPointerToArray(vertexToAdd, &out, size);

這些重寫都沒有解決微妙的內存泄漏問題。 麻煩的是,如果你用返回值覆蓋傳遞給realloc()但是realloc()失敗的值,則會丟失指向(仍然)分配的數組的指針 - 泄漏內存。 當你使用realloc() ,使用如下的習語:

Vertex *new_space = realloc(out, newSize * sizeof(Vertex));
if (new_space != 0)
    out = new_space;
else
    ...deal with error...but out has not been destroyed!...

請注意,使用realloc()添加一個新項會導致(可能導致)二次行為。 你最好分配一大塊內存 - 例如,將分配的空間加倍:

int newSize = *size * 2;

如果您擔心過度分配,則在讀取循環結束時,您可以使用realloc()將分配的空間縮小到數組的確切大小。 但是,還有更多的簿記要做; 您需要值:分配給數組的頂點數量,以及實際使用的頂點數量。

最后,至少現在,請注意,您應該真正無情地保持一致,並使用addPointerToArray()將前三個條目添加到數組中。 我可能會使用類似於這個(未經測試的)代碼的東西:

struct VertexList
{
    size_t    num_alloc;
    size_t    num_inuse;
    Vertex   *list;
};

void initVertexList(VertexList *array)
{
    // C99: *array = (VertexList){ 0, 0, 0 };
    // Verbose C99: *array = (VertexList){ .num_inuse = 0, .num_alloc = 0, .list = 0 };
    array->num_inuse = 0;
    array->num_alloc = 0;
    array->list      = 0;
}

void addPointerToArray(Vertex v1, VertexList *array)
{
    if (array->num_inuse >= array->num_alloc)
    {
        assert(array->num_inuse == array->num_alloc);
        size_t new_size = (array->num_alloc + 2) * 2;
        Vertex *new_list = realloc(array->list, new_size * sizeof(Vertex));
        if (new_list == 0)
            ...deal with out of memory condition...
        array->num_alloc = new_size;
        array->list      = new_list;
    }
    array->list[array->num_inuse++] = v1;
}

這使用realloc()的反直覺屬性,如果傳入的指針為null,它將執行malloc() 你可以改為對array->list == 0進行檢查,然后使用malloc() then和realloc()

您可能會注意到此結構也簡化了調用代碼; 你不再需要處理單獨的int *size; 在主程序中(及其內存分配); 大小有效地捆綁到VertexList結構中作為num_inuse 主程序現在可能開始:

int main(void)
{
    VertexList array;
    initVertexList(&array);
    addPointerToArray((Vertex){ 1, 1 }, &array);  // C99 compound literal
    addPointerToArray((Vertex){ 2, 2 }, &array);
    addPointerToArray((Vertex){ 3, 3 }, &array);
    addPointerToArray((Vertex){ 9, 9 }, &array);

    for (int i = 0; i < array->num_inuse; i++)
        printf("Vertex %d: (%d, %d)\n", i, array->list[i].x, array->list[i].y, i);

    return 0;
}

(巧合的是,這個序列只會調用一次內存分配,因為新的大小(old_size + 2) * 2第一次為數組分配了4個元素。通過添加新的點或者通過添加新的點很容易進行重新分配將公式精煉為(old_size + 1) * 2 ,或者......

如果您計划從內存分配失敗中恢復(而不是僅僅在它發生時退出),那么您應該修改addPointerToArray()以返回狀態(成功,不成功)。

此外,函數名稱應該是addPointToArray()addVertexToArray() ,甚至是addVertexToList()

我有一些建議供你考慮:
1. 使用realloc不要使用相同的輸入和輸出參數,因為如果內存分配失敗並且先前指向的內存泄漏,它可以返回NULL realloc可以返回新的內存塊(感謝@Jonathan Leffler指出,我錯過了這個)。 您可以將代碼更改為以下行:

Vertex * new_out = realloc(out,  newSize * sizeof(Vertex));
if( NULL != new_out )
{    
    out = new_out;
    out[(*size)] = v1;
}
else
{
 //Error handling & freeing memory
}

2.在內存失敗時為malloc調用添加NULL檢查並處理錯誤。
3.失去了free電話。
4.將addPointerToArray()的返回類型從void更改為bool以指示添加是否成功。 如果realloc失敗你可以返回失敗說,假,否則你可以返回成功說,是的。
@MatthewD已經指出了與過多副本等相關的其他觀察結果。
@Jonathan Leffler幾乎沒有什么好的觀察(希望這有幫助!

您的示例程序對我來說很好。 我在Linux上使用gcc 4.1.1。

但是,如果您的實際程序與示例程序類似,則效率相當低!

例如,你的程序將存儲了很多:結構副本-初始化out ,路過頂點addPointerToArray()通過內存復制realloc()

通過指針而不是通過復制傳遞結構。

如果需要大量增加列表類型的大小,最好使用鏈表,樹或其他結構(取決於稍后需要的訪問類型)。

如果你只需要一個矢量類型,那么實現動態大小矢量的標准方法是分配一塊內存(比如16個頂點的空間),並在每次空間不足時將其大小加倍。 這將限制所需的realloc數量。

嘗試這些更改,它應該工作。

void addPointerToArray(Vertex v1, Vertex (*out)[], int *size)
{
    int newSize = *size;
    newSize++;

    *out = realloc(out, newSize * sizeof(Vertex));
    *out[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

並調用函數

addPointerToArray(vertexToAdd, &out, size);
  • 有一種簡單的方法可以解決這些類型的問題(您可能已經知道這一點)。 當你將一個參數傳遞給一個函數時,想一想究竟是什么進入了堆棧,然后結合這樣一個事實:當你出現函數時,你所做的改變會對堆棧上存在的變量產生影響。 這種思考應該解決與傳遞參數相關的大多數問題。

  • 進入優化部分,選擇正確的數據結構對於任何項目的成功至關重要。 就像上面提到的那樣,鏈接列表是比數組更好的數據結構。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM