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
}
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
}
Yanıtlar:
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 len
kullanı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.
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.
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. len
Belirli 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 malloc
ve free
.
char result [sizeof(char)]
bir boyut dizisidir 1
(çünkü sizeof(char)
bire eşittir), bu nedenle atama kesilecektir some chars
.
str
bir işaretçiye bozulur , bu nedenle sizeof
sisteminizdeki işaretçi boyutuna bağlı olarak dört veya sekiz olacaktır.
char* result = alloca(len);
yığın üzerinde tahsis edebilirsiniz. Aynı temel etkiye (ve aynı temel sorunlara) sahiptir
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.
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.
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.
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.
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 realloc
ve calloc
aynı 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.
Ç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 len
yeterince 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 calloc
veya malloc
&free
) kullanmanın dezavantajları vardır:
başarısız olabilir ve her zaman arızayı test etmelisiniz (örn. calloc
veya malloc
geri dönüyor NULL
).
daha yavaştır: başarılı bir VLA tahsisi birkaç nanosaniye sürer, başarılı olan malloc
birkaç 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: free
yalnızca sivri bölgenin artık kullanılmadığından emin olduğunuzda yapabilirsiniz . Durumda da hem diyebileceğimiz calloc
ve free
aynı 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.buf
result
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, malloc
yeterli bellek olmasa bile bazı işaretçiler verir). Bu, Linux makinelerimde sevmediğim ve genellikle devre dışı bıraktığım bir özellik.