Hallo zusammen,
ich habe ein Programm, das memcpy verwendet.
Allerdings stuerzt das Programm ab, was, glaube ich, an "region overlap"
bei der Funktion memcpy() liegt.
1
memcpy(80006e80,80006e84,4):
Da ein "region overlap" bei memcpy() zu Problemen fuehren kann, soll man
wohl eher memmove() verwenden.
Ich habe ebenfalls gelesen, dass die meisten Prozessoren oder Compiler
memcpy() wie memmove() behandeln, um solch ein Laufzeitfehler
auszuschliessen.
Ich benutze einen ARM Cortex A9 und Linaro-gcc.
Hier wird leider nichts wegoptimiert und ich laufe ich den Fehler.
Ich habe hier legacy SW, die ich nur ungern veraendern moechte.
Weiss jmd ob ich Compiler-Flags setzen kann, die mir das Problem loesen?
Oder muss ich fuer diese Plattform den Code umschreiben?
DANKE!
Das Verhalten von memcpy() ist bei überlappenden Speicherbereichen
tatsächlich undefiniert.
olpo schrieb:> memcpy(80006e80, 80006e84, 4):
Hier kann ich allerdings beim besten Willen keine Überlappung erkennen -
memmove() wird da nichts besser machen, würde ich mal unterstellen.
Ich habe das hier mal auf einem ARM mit Linaro-Compiler getestet.
Das Programm macht exakt das, was es soll. Der memcpy() funktioniert.
Die richtigen Bytes werden kopiert. Nichts stürzt ab.
Von daher glaube ich nicht an ein memcpy Problem. Zumal memcpy ja in
tausenden Programmen benutzt wird.
Ich denke mehr, dein Problem liegt in den Zeilen, die du hier nicht
gepostet hast.
PittyJ schrieb:> Das Programm macht exakt das, was es soll. Der memcpy() funktioniert.> Die richtigen Bytes werden kopiert. Nichts stürzt ab.>
Der einzige Unterschied zwischen memcpy() und memmove() ist der, daß
Letzteres vor dem Kopieren auf Überlappung der Speicherbereiche prüft
und entsprechend "aufwärts" oder "abwärts" kopiert, um sich nicht selbst
die Quelle zu überschreiben.
Da hier nichts überlappt, macht es keinen Unterschied (außer, daß
memmove() hier unnötigerweise ein klein wenig langsamer ist).
Ich kann überdies auch keinen Grund erkennen, weshalb memcpy bei
Überlappung die Grätsche machen sollte, so hier eine gewesen wäre. Es
kommt bloss nicht unbedingt das raus, was man haben will.
A. K. schrieb:> Ich kann überdies auch keinen Grund erkennen, weshalb memcpy bei> Überlappung die Grätsche machen sollte, so hier eine gewesen wäre. Es> kommt bloss nicht unbedingt das raus, was man haben will.
Wenn wir schon dabei sind: manchmal bin ich mir nicht sicher, ob
bestimmte Fragen wirklich so ernst gemeint sind, wie's auf den ersten
Blick aussieht.
Ich persönlich käme beispielsweise nicht unbedingt (zumindest nicht auf
einem 32-bit Dualcore) auf die Idee, ein - offensichtlich perfekt
ausgerichtetes - int per memcpy() zu kopieren oder vielleicht doch
lieber per memmove(), weil es sich ja selber überlappen könnte.
Ein einfaches
1
*(int*)0x80006e80=*(int*)0x80006e84;
würde hier ohne große Diskussion dasselbe (richtige) tun, bloß viel
schneller...
Markus F. schrieb:> Ein einfaches> * (int *) 0x80006e80 = * (int *) 0x80006e84;>> würde hier ohne große Diskussion dasselbe (richtige) tun, bloß viel> schneller...
und da bist du dir sicher? Eventuell wird ja erst ins Register geladen,
dann wieder an die neue stelle geschrieben.
beim memcopy kann es die cpu direkt im ram machen.
Ich finde memcopy hier besser und lesbarer.
Markus F. schrieb:> Lach! 8-)>> Dann werde ich künftig keine direkten Zuweisungen in der Form> int i = 4711;>> mehr machen (ist ja irgendwie auch viel zu einfach). Bloß noch:> int i;> const int c = 4711;>> memcpy(&i, &c, sizeof(int));>> Ist ja auch viel lesbarer. Wenn's hilft...
einmal sind es variabel die schon im Register sind, einmal ist es nur
RAM. Das ist der unterschied.
Außerdem würde memcpy auch auf 8 und 16bit cpus laufen, ohne das man
etwas ändern muss.
Wir kommen jetzt ein wenig ins philosophische. Ich bin kein
ARM-Spezialist, aber wenn ich micht nicht ganz täusche, kennt die
ARM-Plattform keine Addressierungsart memory-to-memory. Da ist immer ein
Register beteiligt.
Peter II schrieb:> beim memcopy kann es die cpu direkt im ram machen.
Nicht bei einem ARM. Mem->Mem hat der nicht.
Und bei x86 ist es eine hochkomplexe Angelegenheit, zu bestimmen, wann
auf welchem Prozessor bei welcher Datenmenge welche Kopierversion
schneller ist.
Klaus Wachtler schrieb:> Besser als ein guter Compiler mit Codeoptimierung wird die Funktion> nicht sein können...
Es gibt ein paar Optimierungsmöglichkeiten, die einem Optimizer nicht so
recht liegen, wie breite SIMD Register, Cache Optimierung, Prefetch etc.
Markus F. schrieb:> Ich persönlich käme beispielsweise nicht unbedingt (zumindest nicht auf> einem 32-bit Dualcore) auf die Idee, ein - offensichtlich perfekt> ausgerichtetes - int per memcpy() zu kopieren oder vielleicht doch> lieber per memmove(), weil es sich ja selber überlappen könnte.>> Ein einfaches> * (int *) 0x80006e80 = * (int *) 0x80006e84;>> würde hier ohne große Diskussion dasselbe (richtige) tun, bloß viel> schneller...
Warum sollte das schneller sein? Vermutlich wird der Code genau der
selbe sein. Zumindest auf dem x86 ist er es.
Rolf Magnus schrieb:> Warum sollte das schneller sein? Vermutlich wird der Code genau der> selbe sein. Zumindest auf dem x86 ist er es.
Nur wenn der Compiler sich wie GCC die Mühe macht, die Funktionalität
von memcpy zu kennen und ggf. selbst zu implementieren. Ruft er hingegen
unabhängig von Randbedingungen einfach nur ebendiese Lib-Funktion auf,
wird die explizite Umgehung von memcpy bei wenigen Bytes stets erheblich
schneller sein.
Auch bei grossen Datenmengen war und ist es trotz prozessorspezifischer
Implementierung per Microcode über die Generationen der Prozessoren
hinweg keineswegs selbstverständlich, dass der REP MOVS Befehl den
schnellsten Weg darstellt. Erst recht nicht, wenn der Programmierer über
den Kontext des Transfers mehr weiss, als es Compiler/Microcode können.
Mein m68k-gcc z.B. hat offensichtlich Befürchtungen, daß die Adresse
auch ungerade sein könnte und kopiert (mit -O3) bei memcpy()
(__builtin_memcpy()) lieber vier Einzelbytes als ein int.
Mit -ffrestanding (nicht ganz unsinnvoll bei embedded Controllern) ist
er ganz vorsichtig und ruft auch mit -O3 lieber memcpy() auf (wie sich
das in dem Fall gehört).
Markus F. schrieb:> Mein m68k-gcc z.B. hat offensichtlich Befürchtungen, daß die Adresse> auch ungerade sein könnte und kopiert (mit -O3) bei memcpy()> (__builtin_memcpy()) lieber vier Einzelbytes als ein int.
Hängt davon ab, wieviele Parameter der Programmierer des Compilers bei
der Optimierung des Builtins berücksichtigt.
A. K. schrieb:> Hängt davon ab, wieviele Parameter der Programmierer des Compilers bei> der Optimierung des Builtins berücksichtigt.
... oder anders gesagt davon, für wie wichtig es eben jener erachtet,
einen memcpy()-Aufruf, der eigentlich ein int kopiert, soweit zu
optimieren, daß er nur noch ein int kopiert (mir persönlich wär' offen
gestanden lieber, er würde seine Kreativität an ein paar anderen,
realeren Optimierungsfällen auslassen).
Womit wir wieder am Anfang der Schleife wären ;-).
casud schrieb:> Manchmal macht memcpy für 4 bytes sehr wohl Sinn, nämlich dann wenn man> "type punning" machen will.
Dafür gibt es aber bereits unions:
1
floatf(unsignedx){
2
union{
3
unsignedu;
4
floatf;
5
}c;
6
c.u=x;
7
returnf;
8
}
Oder darf der compiler dass wegen der aliasing rule wegobtimieren?
A. K. schrieb:> Rolf Magnus schrieb:>> Warum sollte das schneller sein? Vermutlich wird der Code genau der>> selbe sein. Zumindest auf dem x86 ist er es.>> Nur wenn der Compiler sich wie GCC die Mühe macht, die Funktionalität> von memcpy zu kennen und ggf. selbst zu implementieren.
Ja. Wir reden hier ja auch über GCC. Inwieweit das andere Compiler auch
machen, weiß ich nicht.
> Auch bei grossen Datenmengen war und ist es trotz prozessorspezifischer> Implementierung per Microcode über die Generationen der Prozessoren> hinweg keineswegs selbstverständlich, dass der REP MOVS Befehl den> schnellsten Weg darstellt. Erst recht nicht, wenn der Programmierer über> den Kontext des Transfers mehr weiss, als es Compiler/Microcode können.
Das hat dann aber nichts mit der Frage zu tun, ob eine Zuweisung
schneller ist als ein memcpy. Einige Programme auf dem PC, die große
Datenmengen im Speicher schnell kopieren müssen, machen beim Start
Performance-Tests, um zu ermitteln, welche Variante die schnellste ist.
Gerade auf dem PC gibt es etliche verschiedene Möglichkeiten. REP MOVS
ist schon lange nicht mehr die schnellste.
Markus F. schrieb:> Mein m68k-gcc z.B. hat offensichtlich Befürchtungen, daß die Adresse> auch ungerade sein könnte und kopiert (mit -O3) bei memcpy()> (__builtin_memcpy()) lieber vier Einzelbytes als ein int.
Welche Adresse? Oben wird die Adresse ja direkt als Zahl angegeben. Daß
die nicht ungerade sein kann, sollte der Compiler eigentlich erkennen
können.
> Mit -ffrestanding (nicht ganz unsinnvoll bei embedded Controllern) ist> er ganz vorsichtig und ruft auch mit -O3 lieber memcpy() auf (wie sich> das in dem Fall gehört).
Das ist auch der große Nachteil von -ffreestanding. Es unterbindet
einige Optimierungen, da der Compiler nicht mehr davon ausgehen darf,
daß sich Funktionen wie memcpy() so verhalten, wie das in ISO-C
festgelegt ist.
Daniel A. schrieb:> casud schrieb:>> Manchmal macht memcpy für 4 bytes sehr wohl Sinn, nämlich dann wenn man>> "type punning" machen will.>> Dafür gibt es aber bereits unions:
Nein, genau dafür sind sie nicht gedacht. Du darfst in C eigentlich nur
das Union-Element lesen, das du als letztes geschrieben hast. Davon
abgeshen sieht es meiner Meinung nach auch nicht sonderlich elegant und
eher umständlich aus, sich extra einen Typ und eine Variable definieren
zu müssen und dann die Daten da rein und wieder rauszukopieren.
Rolf Magnus schrieb:> Welche Adresse? Oben wird die Adresse ja direkt als Zahl angegeben. Daß> die nicht ungerade sein kann, sollte der Compiler eigentlich erkennen> können.
Haha, tut er aber nicht, der blöde Hund.
Die Einzelbytekopiererei wird erst dann durch eine schnelle int-Kopie
ersetzt, wenn -mno-strict-align in der Kommandozeile steht.
Dann allerdings auch, wenn explizit von/auf ungerade Adressen kopiert
wird (der Adressfehler-Handler wird's schon richten). :-o
Kenne deinen Compiler - jetzt hab' ich auch was gelernt ;)
Hallo,
kann das etwas mit der Byte-Reihenfolge zu tun haben?
Also ich kompiliere auf einer Intel Little-Endian Maschine.
Das ganze laeuft dann auf ARMv7 mit einem BareMetalOS, bei dem ich mir
nicht sicher bin, ob das nicht doch Big-Endian kompiliert wurde.
Oder wird __BYTE_ORDER zur Laufzeit und nicht vom Compiler festgelegt?
Zumindest kriege ich keinen Error, wenn ich Little-Endian unten
auskommentiere.
Das verwirrt mich alles...
ok, anscheinend hat hat ARM Schwierigkeiten mit unaligned memory. Intel
buegelt das angeblich aus.
1
unsignedchar*dest;
2
unsignedlongsrc;
3
// ...
4
5
memcpy(&dest,&src,4);
Das Problem ist wohl, dass "dest" vom Typ char ist und somit kleiner als
die zu kopierenden 4 Byte, oder?
Oder kopiere ich an die Adresse vom Pointer *dest?
Egal,
das ganze ist Legacy Code, den ich jetzt auf ARM laufen lassen will.
Wie behebe ich das Problem am saubersten, ohne viel in den Code
einzugreifen.
olpo schrieb:> ok, anscheinend hat hat ARM Schwierigkeiten mit unaligned memory.
1
unsignedchar*dest;
2
unsignedlongsrc;
3
memcpy(&dest,&src,4);
> Das Problem ist wohl, dass "dest" vom Typ char ist.
Nein, dest ist ein pointer auf char daten. wenn du den poiner dest
referenzierst, hast du einen pointer auf einen pointer auf char daten.
Der wert des Pointers (dessen adresse), welche du überschreibst, wobei
du annimst, aber nicht prüfst, ob diese grösser oder gleich 4 ist, ist
garantiert aligned.
olpo schrieb:> ok, anscheinend hat hat ARM Schwierigkeiten mit unaligned memory.
Nicht generell. Manche können es, manche nicht.
Nur betrifft das den gezeigten Fall nicht.
olpo schrieb:> Hallo zusammen,>> ich habe ein Programm, das memcpy verwendet.> Allerdings stuerzt das Programm ab, was, glaube ich, an "region overlap"> bei der Funktion memcpy() liegt.
1
memcpy(80006e80,80006e84,4):
> Da ein "region overlap" bei memcpy() zu Problemen fuehren kann, soll man> wohl eher memmove() verwenden.> Ich habe ebenfalls gelesen, dass die meisten Prozessoren oder Compiler> memcpy() wie memmove() behandeln, um solch ein Laufzeitfehler> auszuschliessen.
Wahrscheinlich ist es ja nur ein Tipp/Übertragungsfehler, aber...
sind die beiden Werte
1
80006e80und80006e84
nicht einfach nur float Zahlen und damit für ein memcpy völlig
ungeeignet?
Sollte es da nicht heftige Warnungen des Compilers geben?
Oder sollte es eigentlich (eventuell mit einem Typecast):
Norbert schrieb:>> Wahrscheinlich ist es ja nur ein Tipp/Übertragungsfehler, aber...>> sind die beiden Werte80006e80 und 80006e84 nicht einfach nur float> Zahlen und damit für ein memcpy völlig> ungeeignet?
Ja, das war nur faul geschrieben und auch mit den falschen Adressen.
Richtig ist es so:
1
memcpy(0x80004e8e,0x80004e3c,4);
2
// Allignment ERROR at memcpy 0x80004e8e
A. K. schrieb:> olpo schrieb:>> ok, anscheinend hat hat ARM Schwierigkeiten mit unaligned memory.>> Nicht generell. Manche können es, manche nicht.> Nur betrifft das den gezeigten Fall nicht.
Also , hier passt alles?
Wie kann es dann zu einem Alignment Error kommen?
Hat jmd Vorschlaege, nach was ich gucken kann?
Genau an dieser memcpy()-Stelle fliegt er raus.
Ok, anscheinend laeuft man mit struct (und ARM) gerne in diese
unalignment Falle.
Bei meinem Fall kommt tatsaechlich ein struct vor. Ich kann aber die
problematische Stelle nicht ausmachen.
Versucht er das struct auf long zu "alignen"?
Häh?
Was hast du damit vor?
buf ist die Adresse einer struct (hoffentlich, je nachdem, wie du die
Funktion aufrufst).
Diese Adresse kopierst du über va_arg (merkwürdigerweis als unsigned
long betrachtet) in die lokale Variable src.
Abgesehen davon, daß die Typen natürlich passen (Adresse in einer
unsigned long), enthält src jetzt die Adresse der struct des Aufrufers.
Dann übergibst du an memcpy die Adresse der lokalen Variable src. memcpy
wird also ab der Adresse 4 Byte kopieren - es wird also die Adresse der
struct kopiert.
Das wolltest so wahrscheinlich nicht haben?
Klaus Wachtler schrieb:> Diese Adresse kopierst du über va_arg (merkwürdigerweis als unsigned> long betrachtet) in die lokale Variable src.
Ähm, nö. Noch schlimmer.
Kopiert wird das auf buf folgende Argument (das möglicherweise da ist
oder auch nicht).
Die Funktion sollte noch eine Ellipsis bekommen, um anzuzeigen, daß eine
variable Argumentliste erwartet wird. Außerdem irgendeinen Parameter,
der ihr sagt, wieviele Argumente grade zu erwarten sind.
So ist das jedenfalls nach Problemen gebettelt.
Markus F. schrieb:> Kopiert wird das auf buf folgende Argument (das möglicherweise da ist> oder auch nicht).
Stimmt, hatte ich ganz übersehen.
Der zusätzliche Versatz macht es dann auch nicht mehr besser :-)
Ich raff den Code auch nicht, aber das ist Legacy Code von IBM(!).
Ich habe den Code nochmal um die Zuweisung an Destination erweitert.
Vielleicht ergibt es so mehr sinn?
Was macht denn va_arg in dem Context?
1
structmyBuf
2
{
3
uint32_tsize;
4
uint32_tused;
5
uint32_tflags;
6
unsignedcharbuffer[MAX_BUFF_SIZE];
7
};
8
9
10
voidfoo(structmyBuf*buf)
11
{
12
va_listargp;
13
unsignedchar*dest;
14
unsignedlongsrc;
15
unsignedchar*buffer=buf->buffer;
16
17
va_start(argp,buf);
18
dest=&buffer[buf->used];
19
20
src=(unsignedlong)va_arg(argp,unsignedlong);
21
22
memcpy(&dest,&src,4);
23
}
Aber ein Alignment Fault laesst sich hier nicht erkennen, oder wie?
olpo schrieb:> Was macht denn va_arg in dem Context?
Kann man nur sagen, wenn Du auch den Aufruf der Funktion postest.
Wenn da nach buf noch ein Argument kommt, müsste man sich genau
anschauen, was da gemacht wird.
Wenn nicht, könnte das möglicherweise ein Versuch sein, erstens den
aufrufenden Stackframe (z.B. für eine Art "setjmp()") zu manipulieren
und zweitens den eigenen Job bis in alle Ewigkeit zu erhalten (was ja
offensichtlich nicht funktioniert hat, sonst wär' der, der das
verbrochen hat, ja noch zu greifen).
Wie bereits weiter oben steht: in memcpy kann es kein Problem mit
alignment geben, weil diese Funktion auch mit ungeraden Adressen
hantieren kann.
Aber du greifst gezielt neben alle irgendwie definierten Werte zu mit
deinem verkorksten va_arg-Müll.
Wenn der Rest des Programms genaus schlecht ist, wie das was man hier
sieht, würde ich auch nicht darauf wetten, daß &buffer[buf->used]
kleiner gleich 4 Byte vor dem Ende des Puffers landet.
Da ist es sinnlos zu spekulieren, warum etwas schief geht.
Es könnte überschriebene Rücksprungadresse geben, verbogene Stackframes
oder ziemlich alles andere geben.
Bestenfalls könnte man gezielter spkulieren, wenn man das ganze Programm
sieht.
Ob sich das jemand antun will, wage ich aber zu bezweifeln.
stdarg.h Kram ohne ... in der Parameterliste zu verwenden fordert
Probleme heraus. Falls der Code im Original exakt so aussieht: kopierter
Mist bleibt Mist. Insbesondere wenn der Mist beispielsweise von x86 nach
ARM kopiert worden sein sollte (andere Technik der Parameterübergabe).
olpo schrieb:> unsigned char *buf;> unsigned long src;>> memcpy(&buf[0], &src, 4);
Da hier buf keinen definierten Wert hat, also ins nirvana zeigt, ist das
schreiben nach buf unsinnig. Zeige doch mal den echten gesammten Code
der Funktion, falls diese existiert, oder zumindest was diese tun soll.
olpo schrieb:> Alignment Error?> buf ist also ein Array von Chars. Und so frisst das ARM nicht, weil Char> != 4Byte ist, oder?
Nein. memcpy() ist so definiert, daß es void-Zeiger akzeptiert und muß
(weil die auf jeden beliebigen Datentyp zeigen können) unabhängig von
der Plattform auch von/auf ungerade Adressen kopieren können.