Dizi veya Malloc?


13

Uygulamamda aşağıdaki kodu kullanıyorum ve iyi çalışıyor. Ama bunu malloc ile yapmanın veya olduğu gibi bırakmanın daha iyi olup olmadığını merak ediyorum?

function (int len)
{
char result [len] = some chars;
send result over network
}

2
Kodun gömülü olmayan bir çevre için hedeflendiği varsayımı var mı?
tehnyit

Yanıtlar:


28

Temel fark, VLA'ların (değişken uzunluklu diziler) tahsis hatalarını tespit etmek için hiçbir mekanizma sağlamamasıdır.

Beyan edersen

char result[len];

ve lenkullanılabilir yığın alanı miktarını aşarsa, programınızın davranışı tanımsızdır. Tahsisin başarılı olup olmayacağını önceden belirleyecek ya da başarılı olup olmadığını belirleyecek bir dil mekanizması yoktur.

Öte yandan, eğer yazarsanız:

char *result = malloc(len);
if (result == NULL) {
    /* allocation failed, abort or take corrective action */
}

başarısızlıkları zarif bir şekilde halledebilir veya en azından programınızın bir hatadan sonra yürütülmeye devam etmeyeceğini garanti edebilirsiniz.

(Pekala, çoğunlukla. Linux sistemlerinde, malloc()karşılık gelen depolama alanı olmasa bile bir adres alanı ayırabilir; daha sonra bu alanı kullanmaya çalışmak OOM Killer'i çağırabilir . Ancak malloc()hata kontrolü hala iyi bir uygulamadır.)

Birçok sistemde başka bir sorun, VLA gibi otomatik nesnelerden daha fazla alan (muhtemelen çok daha fazla alan) malloc()olmasıdır.

Philip'in cevabının daha önce de belirtildiği gibi, VLA'lar C99'a eklendi (özellikle Microsoft bunları desteklemiyor).

Ve VLA'lar C11'de isteğe bağlı hale getirildi. Muhtemelen çoğu C11 derleyicisi bunları destekleyecektir, ancak buna güvenemezsiniz.


14

Değişken uzunlukta otomatik diziler C99'da C'ye dahil edildi.

Eski standartlarla geriye doğru karşılaştırılabilirlik konusunda endişeleriniz yoksa, sorun değil.

Genel olarak, eğer işe yararsa, dokunmayın. Önceden optimizasyon yapmayın. Özel özellikler ekleme veya bir şeyler yapmanın akıllıca yollarını ekleme konusunda endişelenmeyin, çünkü genellikle kullanmayacaksınız. Basit tutun.


7
"Çalışıyorsa, dokunmayın" dictumuna katılmıyorum. Yanlış bazı kod "çalışır" inanıyorum "çalışır" bazı kod sorunları çözmek için neden olabilir. İnanç, bazı kodların şu anda çalıştığını geçici olarak kabul etmekle değiştirilmelidir.
Bruce Ediger

2
Belki daha iyi "çalışır" bir sürüm yapana kadar dokunmayın ...
H_7

8

Derleyiciniz değişken uzunluklu dizileri destekliyorsa, tek tehlike len, gülünç derecede büyük olduğunda, bazı sistemlerde yığını taşmaktır. lenBelirli bir sayıdan daha büyük olmayacağından eminseniz ve yığınızın maksimum uzunlukta bile taşmayacağını biliyorsanız, kodu olduğu gibi bırakın; aksi halde, onu yeniden yazmak mallocve free.


buna c99 olmayan fonksiyonda (char []) {char sonuç [sizeof (char)] = bazı karakter; sonucu ağ üzerinden gönder}
Dev Bag

@DBBag char result [sizeof(char)]bir boyut dizisidir 1(çünkü sizeof(char)bire eşittir), bu nedenle atama kesilecektir some chars.
dasblinkenlight

bunun için üzgünüm, demek istediğim bu şekilde fonksiyon (char str []) {char sonuç [sizeof (str)] = bazı karakter; sonucu ağ üzerinden gönder}
Dev Bag

4
@DevBag Bu da işe yaramayacak - str bir işaretçiye bozulur , bu nedenle sizeofsisteminizdeki işaretçi boyutuna bağlı olarak dört veya sekiz olacaktır.
dasblinkenlight

2
Değişken uzunluklu diziler içermeyen bir C sürümü kullanıyorsanız, bunu char* result = alloca(len);yığın üzerinde tahsis edebilirsiniz. Aynı temel etkiye (ve aynı temel sorunlara) sahiptir
Robotu Gort

6

Ben bellek parçalanması, sarkan işaretçiler, vb olmadan bir çalışma zamanı ayrılmış dizi olabilir fikri seviyorum. Ancak, diğerleri bu çalışma zamanı ayırma sessizce başarısız olabilir işaret etti. Bu yüzden bir Cygwin bash ortamında gcc 4.5.3 kullanarak denedim:

#include <stdio.h>
#include <string.h>

void testit (unsigned long len)
{
    char result [len*2];
    char marker[100];

    memset(marker, 0, sizeof(marker));
    printf("result's size: %lu\n", sizeof(result));
    strcpy(result, "this is a test that should overflow if no allocation");
    printf("marker's contents: '%s'\n", marker);
}

int main(int argc, char *argv[])
{
    testit(100);
    testit((unsigned long)-1);  // probably too big
}

Çıktı şuydu:

$ ./a.exe
result's size: 200
marker's contents: ''
result's size: 4294967294
marker's contents: 'should overflow if no allocation'

İkinci çağrıda iletilen aşırı büyük uzunluk açıkça hataya neden oldu (işaretleyiciye [] taşması). Bu, bu tür bir kontrolün kusursuz olduğuna (aptallar akıllı olabilir!) Veya C99 standartlarına uygun olduğu anlamına gelmez, ancak bu endişeniz varsa yardımcı olabilir.

Her zamanki gibi YMMV.


1
+1 bu çok yararlı: 3
Kokizzu

İnsanların iddiaları ile birlikte gitmek için bazı kod olması her zaman güzel! Teşekkürler ^ _ ^
Musa Al-hassy

3

Genel olarak, yığın, verilerinizi koymak için en kolay ve en iyi yerdir.

Ben sadece beklediğiniz en büyük dizi tahsis VLAs sorunları önlemek.

Bununla birlikte, yığının en iyi olduğu ve malloc ile uğraşmanın çabaya değer olduğu durumlar vardır.

  1. Ne zaman büyük ama değişken miktarda veri. Büyük, ortamınıza bağlıdır> gömülü sistemler için 1K, bir Enterprise sunucusu için> 10MB.
  2. Verileriniz rutinden çıktıktan sonra da kalmasını istediğinizde, örneğin verilerinize bir işaretçi döndürürseniz. kullanma
  3. Statik işaretçi ve malloc () kombinasyonu genellikle büyük bir statik dizi tanımlamaktan daha iyidir;

3

Gömülü programlamada, malloc ve serbest işlemler sık ​​olduğunda malloc yerine statik dizi kullanırız. Gömülü sistemde bellek yönetimi eksikliği nedeniyle, sık ayırma ve serbest işlemler bellek parçasına neden olur. Ancak, dizinin maksimum boyutunu tanımlamak ve statik yerel dizi kullanmak gibi bazı zor yöntemler kullanmalıyız.

Uygulamanız Linux veya Windows'ta çalışıyorsa, dizi veya malloc kullanmak önemli değildir. Önemli nokta, tarih yapınızı ve kod mantığınızı kullandığınız yerde yatmaktadır.


1

Henüz kimsenin bahsetmediği bir şey, bir VLA tahsisinin sadece yığın işaretçisini (en azından GCC'de) ayarlaması gerektiğinden, değişken uzunluk dizisi seçeneğinin muhtemelen malloc / free'den çok daha hızlı olacağıdır.

Bu nedenle, bu işlev sıkça adlandırılan bir işlevse (elbette profilleme ile belirleyeceksiniz), VLA iyi bir optimizasyon seçeneğidir.


1
Sizi alan dışı bir duruma ittiği noktaya kadar iyi görünecek. Dahası, aslında yığın sınırına ulaşan kodunuz olmayabilir ; bir kütüphane veya sistem çağrısında ısırmaya (veya kesmeye) neden olabilir.
Donal Fellows

@ Donal Performance her zaman hıza karşı hafızanın değiş tokuşudur. Eğer birkaç megabaytlık dizileri ayıracaksanız, fonksiyon özyinelemediği sürece, birkaç kilobayt için bile bir noktanız var, iyi bir optimizasyon.
JeremyP

1

Bu, yardımcı olabilecek sorun için kullandığım çok yaygın bir C çözümüdür. VLA'ların aksine, patolojik vakalarda yığın taşması riski yoktur.

/// Used for frequent allocations where the common case generally allocates
/// a small amount of memory, at which point a heap allocation can be
/// avoided, but rare cases also need to be handled which may allocate a
/// substantial amount. Note that this structure is not safe to copy as
/// it could potentially invalidate the 'data' pointer. Its primary use
/// is just to allow the stack to be used in common cases.
struct FastMem
{
    /// Stores raw bytes for fast access.
    char fast_mem[512];

    /// Points to 'fast_mem' if the data fits. Otherwise, it will point to a
    /// dynamically allocated memory address.
    void* data;
};

/// @return A pointer to a newly allocated memory block of the specified size.
/// If the memory fits in the specified fast memory structure, it will use that
/// instead of the heap.
void* fm_malloc(struct FastMem* mem, int size)
{
    // Utilize the stack if the memory fits, otherwise malloc.
    mem->data = (size < sizeof mem->fast_mem) ? mem->fast_mem: malloc(size);
    return mem->data;
}

/// Frees the specified memory block if it has been allocated on the heap.
void fm_free(struct FastMem* mem)
{
    // Free the memory if it was allocated dynamically with 'malloc'.
    if (mem->data != mem->fast_mem)
        free(mem->data);
    mem->data = 0;
}

Sizin durumunuzda kullanmak için:

struct FastMem fm;

// `result` will be allocated on the stack if 'len <= 512'.
char* result = fm_malloc(&fm, len);

// send result over network.
...

// this function will only do a heap deallocation if 'len > 512'.
fm_free(&fm, result);

Yukarıdaki durumda bu, dize 512 bayt veya daha azına sığıyorsa yığını kullanmaktır. Aksi takdirde bir yığın ayırma kullanır. Diyelim ki, zamanın% 99'u, dize 512 bayt veya daha azına sığarsa yararlı olabilir. Ancak, dize zaman zaman kullanıcının klavyesinde uykuya daldığı 32 kilobayt olduğu yerde veya bunun gibi bir şeyle uğraşmanız gerekebilecek çılgın egzotik bir durum olduğunu varsayalım. Bu, her iki durumun da sorunsuz bir şekilde ele alınmasını sağlar.

Üretimde kullanılan asıl versiyonu da kendi sürümü vardır reallocve callocaynı konsept üzerine inşa standart uygun C ++ veri yapıları ve benzeri gibi, ancak kavramı göstermek için gereken minimum ekstre edildi.

Etrafında kopyalamanın tehlikeli olduğu uyarısı var ve içinden ayrılmış işaretçileri döndürmemelisiniz ( FastMemörnek tahrip edildiğinde geçersiz hale gelebilirler ). Her zaman yığını / VLA'ları kullanmanın cazip olacağı yerel bir işlevin kapsamındaki basit durumlar için kullanılması gerekir, aksi takdirde bazı nadir durumlar arabellek / yığın taşmalarına neden olabilir. Genel amaçlı bir ayırıcı değildir ve bu şekilde kullanılmamalıdır.

Aslında eskiden C89 kullanan eski bir kod tabanındaki bir duruma yanıt olarak, bir kullanıcının 2047 karakterden uzun bir adı olan bir öğeyi adlandırmayı başardığı eski bir ekibin asla olmayacağını düşündüm (belki de klavyesinde uykuya daldı) ). Meslektaşlarım aslında çeşitli yerlerde tahsis edilen dizilerin boyutunu 16.384'e yükseltmeye çalıştı, bu noktada saçma hale geldiğini düşündüm ve daha az tampon taşması riski karşılığında daha büyük bir yığın taşması riski alıyorum. Bu, sadece birkaç satır kod ekleyerek bu durumları düzeltmek için takılması çok kolay bir çözüm sağladı. Bu, genel durumun çok verimli bir şekilde ele alınmasına ve yığının yazılımın çökmesini gerektiren çılgın nadir durumlar olmadan kullanılmasına izin verdi. Ancak, ben' VLA'lar bizi yığın taşmalarına karşı hala koruyamadığı için C99'dan sonra bile faydalı buldular. Bu, küçük ayırma istekleri için yığıntan havuzlar oluşturabilir.


1

Çağrı yığını zaman ile sınırlıdır. Linux veya Windows gibi genel işletim sistemlerinde sınır bir veya birkaç megabayttır (ve bunu değiştirmenin yollarını bulabilirsiniz). Bazı çok iş parçacıklı uygulamalarda, daha düşük olabilir (çünkü daha küçük bir yığınla iş parçacıkları oluşturulabilir). Gömülü sistemlerde, birkaç kilobayt kadar küçük olabilir. İyi bir kural, birkaç kilobayttan daha büyük çağrı çerçevelerinden kaçınmaktır.

Bu nedenle bir VLA kullanmak, yalnızca lenyeterince küçük olduğundan eminseniz mantıklıdır (en fazla birkaç on binlerce). Aksi takdirde bir yığın taşması var ve bu tanımsız bir davranış , çok korkutucu bir durum.

Bununla birlikte, manuel C dinamik bellek ayırma (örneğin callocveya malloc&free ) kullanmanın dezavantajları vardır:

  • başarısız olabilir ve her zaman arızayı test etmelisiniz (örn. callocveya mallocgeri dönüyor NULL).

  • daha yavaştır: başarılı bir VLA tahsisi birkaç nanosaniye sürer, başarılı olan mallocbirkaç mikrosaniye (iyi durumlarda, sadece bir mikrosaniyenin bir kısmı) veya daha fazlasına ( daralmayı içeren patolojik durumlarda , çok daha fazlasına) ihtiyaç duyabilir .

  • kodlanması çok daha zordur: freeyalnızca sivri bölgenin artık kullanılmadığından emin olduğunuzda yapabilirsiniz . Durumda da hem diyebileceğimiz callocve freeaynı rutininde.

Çoğu zaman sizin result (çok kötü bir isim, otomatik bir VLA değişkeninin adresini asla döndürmemelisiniz, bu yüzden aşağıda yerine kullanıyorum ) küçük olduğunu biliyorsanız, özel durumda olabilirsiniz, örn.bufresult

char tinybuf[256];
char *buf = (len<sizeof(tinybuf))?tinybuf:malloc(len);
if (!buf) { perror("malloc"); exit(EXIT_FAILURE); };
fill_buffer(buf, len);
send_buffer_on_network(buf, len);
if (buf != tinybuf) 
  free(buf);

Bununla birlikte, yukarıdaki kod daha az okunabilir ve muhtemelen erken optimizasyondur. Ancak saf bir VLA çözümünden daha sağlamdır.

PS. Bazı sistemlerde (örn. Bazı Linux dağıtımları varsayılan olarak etkinleştirilmiştir) bellek fazla çalışması vardır (bu, mallocyeterli bellek olmasa bile bazı işaretçiler verir). Bu, Linux makinelerimde sevmediğim ve genellikle devre dışı bıraktığım bir özellik.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.