Yanıtlar:
İle memcpy
, hedef kaynakla hiçbir şekilde örtüşemez. Onunla memmove
olabilir. Bu , aynı varsayımları yapamayacağından memmove
çok az daha yavaş olabileceği anlamına gelir memcpy
.
Örneğin, memcpy
adresleri her zaman düşükten yükseğe kopyalayabilir. Hedef kaynaktan sonra çakışırsa, bu kopyalanmadan önce bazı adreslerin üzerine yazılacağı anlamına gelir. memmove
bu durumda bunu algılar ve diğer yönde (yüksekten alta doğru) kopyalar. Ancak, bunu kontrol etmek ve başka (muhtemelen daha az verimli) bir algoritmaya geçmek zaman alır.
i = i++ + 1
; derleyici tam olarak bu kodu yazmanızı yasaklamaz, ancak bu talimatın sonucu herhangi bir şey olabilir ve farklı derleyiciler veya CPU'lar burada farklı değerler gösterecektir.
memmove
örtüşen belleği memcpy
kaldırabilir, yapamaz.
Düşünmek
char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up
Açıkçası kaynak ve hedef artık örtüşüyor, "bar" ile "-bar" ın üzerine yazıyoruz. memcpy
Kaynak ve hedefin örtüşmesi, bu durumda ihtiyacımız olan durumlarda tanımsız bir davranıştır memmove
.
memmove(&str[3],&str[4],4); //fine
Arasındaki temel fark memmove()
ve memcpy()
içinde olmasıdır memmove()
bir tampon geçici hafıza - - kullanılır, bu nedenle örtüşen riski yoktur. Öte yandan, memcpy()
veriyi kaynağın gösterdiği konumdan hedefin gösterdiği konuma doğrudan kopyalar . ( http://www.cplusplus.com/reference/cstring/memcpy/ )
Aşağıdaki örnekleri düşünün:
#include <stdio.h>
#include <string.h>
int main (void)
{
char string [] = "stackoverflow";
char *first, *second;
first = string;
second = string;
puts(string);
memcpy(first+5, first, 5);
puts(first);
memmove(second+5, second, 5);
puts(second);
return 0;
}
Beklediğiniz gibi, bu çıktı:
stackoverflow
stackstacklow
stackstacklow
Ancak bu örnekte sonuçlar aynı olmayacak:
#include <stdio.h>
#include <string.h>
int main (void)
{
char string [] = "stackoverflow";
char *third, *fourth;
third = string;
fourth = string;
puts(string);
memcpy(third+5, third, 7);
puts(third);
memmove(fourth+5, fourth, 7);
puts(fourth);
return 0;
}
Çıktı:
stackoverflow
stackstackovw
stackstackstw
Bunun nedeni "memcpy ()" nin aşağıdakileri yapmasıdır:
1. stackoverflow
2. stacksverflow
3. stacksterflow
4. stackstarflow
5. stackstacflow
6. stackstacklow
7. stackstacksow
8. stackstackstw
memmove()
Bir tampon kullanmak için uygulamasının gerekli olduğunu düşünmüyorum . Tamamen yerinde taşıma hakkına sahiptir (her okuma aynı adrese herhangi bir yazmadan önce tamamlandığı sürece).
Her ikisini de uygulamanız gerektiğini varsayarsak, uygulama şöyle görünebilir:
void memmove ( void * dst, const void * src, size_t count ) {
if ((uintptr_t)src < (uintptr_t)dst) {
// Copy from back to front
} else if ((uintptr_t)dst < (uintptr_t)src) {
// Copy from front to back
}
}
void mempy ( void * dst, const void * src, size_t count ) {
if ((uintptr_t)src != (uintptr_t)dst) {
// Copy in any way you want
}
}
Ve bu, farkı oldukça iyi açıklamalı. memmove
Hala eğer güvenli şekilde her zaman kopya src
ve dst
örtüşme, oysa memcpy
kullanırken dokümantasyon söylediği gibi sadece umursamıyor memcpy
iki bellek alanları, olmamalıdır örtüşme.
Örneğin, memcpy
"önden arkaya" kopyalar ve bellek blokları bu şekilde hizalanmışsa
[---- src ----]
[---- dst ---]
ilk baytı kopyalama src
için dst
şimdiden son bayt içeriği yok eden src
bu kopyalanmış önce. Yalnızca "arkadan öne" kopyalamak doğru sonuçlara yol açar.
Şimdi değiştirin src
ve dst
:
[---- dst ----]
[---- src ---]
Bu durumda sadece "önden arkaya" kopyalamak güvenlidir, çünkü "arkadan öne" src
kopyalamak, ilk baytı kopyalarken zaten ön tarafının yakınında yok olur.
memmove
Yukarıdaki uygulamanın gerçekten örtüşüp örtüşmediğini test etmediğini bile fark etmişsinizdir, sadece göreceli konumlarını kontrol eder, ancak bu tek başına kopyayı güvenli hale getirecektir. Gibi memcpy
, genellikle herhangi bir sistem üzerinde bellek kopyalamak mümkün olan en hızlı şekilde kullanır, memmove
genellikle oldukça olarak uygulanır:
void memmove ( void * dst, const void * src, size_t count ) {
if ((uintptr_t)src < (uintptr_t)dst
&& (uintptr_t)src + count > (uintptr_t)dst
) {
// Copy from back to front
} else if ((uintptr_t)dst < (uintptr_t)src
&& (uintptr_t)dst + count > (uintptr_t)src
) {
// Copy from front to back
} else {
// They don't overlap for sure
memcpy(dst, src, count);
}
}
Bazen, memcpy
her zaman "önden arkaya" veya "arkadan öne" kopyalarsa , çakışan durumlardan birinde memmove
de kullanılabilir memcpy
, ancak memcpy
verilerin nasıl hizalandığına ve / veya ne kadar verinin olacağına bağlı olarak farklı bir şekilde bile kopyalayabilir kopyalanır, bu nedenle memcpy
sisteminizde nasıl kopyaları test etseniz bile , o test sonucunun her zaman doğru olduğuna güvenemezsiniz.
Hangisini arayacağınıza karar verirken bu sizin için ne anlama geliyor?
Bundan emin olmadığınız src
ve dst
çakışmadığınızdan emin değilseniz memmove
, her zaman doğru sonuçlara yol açacağı ve genellikle ihtiyaç duyduğunuz kopya vakası için mümkün olduğu kadar hızlı olacağı için arayın .
Bundan eminseniz src
ve dst
çakışmıyorsanız, memcpy
sonuç için hangisini aradığınız önemli olmayacağı için arayın, bu durumda her ikisi de doğru şekilde çalışacak, ancak memmove
asla daha hızlı olmayacak memcpy
ve şanssızsanız, hatta olabilir daha yavaş olun, böylece yalnızca aramayı kazanabilirsiniz memcpy
.
basitçe ISO / IEC: 9899 standardından iyi tanımlanmıştır.
7.21.2.1 Memcpy işlevi
[...]
2 memcpy işlevi s2 ile gösterilen nesneden n karakteri s1 ile gösterilen nesneye kopyalar. Örtüşen nesneler arasında kopyalama gerçekleşirse, davranış tanımsızdır.
Ve
7.21.2.2 memmove işlevi
[...]
2 memmove işlevi s2 ile gösterilen nesneden n karakteri s1 ile gösterilen nesneye kopyalar. Kopyalama, s2 ile gösterilen nesnedeki n karakter ilk önce s1 ve s2 ile gösterilen nesnelerle örtüşmeyen geçici bir n karakter dizisine kopyalanır ve ardından geçici dizideki n karakterleri içine kopyalanır gibi gerçekleşir. s1 ile gösterilen nesne.
Soruya göre genellikle hangisini kullandığım, ihtiyacım olan işlevselliğe bağlıdır.
Düz metinde memcpy()
izin vermez s1
ve s2
örtüşmez memmove()
.
Uygulamanın iki bariz yolu vardır mempcpy(void *dest, const void *src, size_t n)
(dönüş değerini göz ardı ederek):
for (char *p=src, *q=dest; n-->0; ++p, ++q)
*q=*p;
char *p=src, *q=dest;
while (n-->0)
q[n]=p[n];
İlk uygulamada, kopya düşük adreslerden yüksek adreslere ve ikinci uygulamada yüksekten alçağa doğru ilerler. Kopyalanacak aralık çakışırsa (örneğin bir çerçeve arabelleğini kaydırırken olduğu gibi), o zaman yalnızca bir işlem yönü doğrudur ve diğeri daha sonra okunacak konumların üzerine yazacaktır.
Bir memmove()
uygulama, en basit haliyle, test edecek dest<src
(platforma bağlı bir şekilde) ve uygun yönünü çalıştıracaktır memcpy()
.
Kullanıcı kodu elbette bunu yapamaz, çünkü dökümden sonra src
ve dst
bazı somut işaretçi türlerinde bile (genel olarak) aynı nesneye işaret etmezler ve bu nedenle karşılaştırılamazlar. Ancak standart kitaplık, Tanımsız Davranışa neden olmadan böyle bir karşılaştırmayı gerçekleştirmek için yeterli platform bilgisine sahip olabilir.
Gerçek hayatta uygulamaların daha büyük aktarımlardan (hizalama izin verdiğinde) ve / veya iyi veri önbelleği kullanımından maksimum performans elde etmek için önemli ölçüde daha karmaşık olma eğiliminde olduğunu unutmayın. Yukarıdaki kod, konuyu olabildiğince basitleştirmek içindir.
memmove çakışan kaynak ve hedef bölgelerle başa çıkabilirken memcpy bunu yapamaz. İkisi arasında memcpy çok daha verimlidir. Yani, eğer mümkünse memcpy'I KULLANMAK daha iyi.
Referans: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain, (Stanford Intro Systems Lecture - 7) Saat: 36:00
memcpy()
ve olmadığını varsayıyorum memcopy()
.