PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : C: Fragen zu memset, memcpy, malloc



HaWe
28.06.2016, 14:16
hallo,
es ist kein spezielles Arduino Thema, sondern generell zu C, aber nachdem es kein C Unterforum gibt, denke ich, hier passt es am besten hin...


ich habe ein paar praktische und ein paar grundsätzliche Fragen zu den C Funktionen memset, memcpy und malloc.

Fangen wir mit dem letzten an:

ich übergebe eine Integer Variable an eine Funktion

foo(int arrlen) {...}

und möchte nun, dass daraus die Funktion temporär einen int16_t Array von genau dieser Länge einrichtet, es soll etwas ähnliches herauskommen (und benutzt werden können) wie
int16_t array[arrlen];
- aber eben mit variabler arrlen, je nachdem, was eben an Parametern an foo() rübergeschoben wird.

Meine Idee war, dafür malloc() zu benutzen.
Ich weiß nun nicht, ob es klug ist, meine (wschl falsche) Idee hier hinzuschreiben, um niemand auf die falsche Fährte zu leiten, daher lass ich es lieber
- denn meine Idee funktioniert nicht aus irgendwelchen Gründen
(was aber durchaus auch mit den nachfolgenden Array-Operationen zu tun haben kann).

Könnte jemand evt mal seine Idee kurz posten? Dann könnte ich es mal mit meinem Ansatz vergleichen.

Danke im vorraus!

Mxt
28.06.2016, 14:46
Hallo,

ich bin nicht so der C-Experte, aber ich denke in neurem C (ISO-Standard C99 oder C11 ?) ist sowas erlaubt


void f(int n)
{
int16_t a[n];

// Beispiel ...
a[0] = 42;
}

das ist dann ein VLA (Variable Length Array).

Ein Problem in der Arduino IDE dürfte sein, dass das eigentlich C++ (nach 2003 Standard) ist. In C++ gibt es keine VLA.

Ab dem 2011er Standard würde in C++ aber gehen


void f(int n)
{
std::array<int16_t, n> a;

// Beispiel ...
a[0] = 42;
}

HaWe
28.06.2016, 14:57
danke, das ist ja jetzt ein ganze anderer Ansatz, sehr schön! 8)
das mit C++ ist nicht meine Welt, das andere aber eine gute Idee - werde ich heute abend testen! :)

Dann zeig ich jetzt mal meine Karten...

int16_t * buf;
buf = (int16_t *) malloc(arrlen);

was ist davon zu halten?

Mxt
28.06.2016, 15:04
Nun, es ist der Anfang vieler Speicherlöcher.


So fängt es an


void Schlump()
{
//...

ding = malloc(irgendwas);

// ...


// Hoffentlich
free(ding);
}


und nach der nächsten Überarbeitung sieht es dann so aus:


void Schlump()
{
//...

ding = malloc(irgendwas);

if (irgendwas())
{
// ...

return; // Huch, ham wa da nich was vergessen
}


// ...


// Hoffentlich
free(ding);
}

HaWe
28.06.2016, 15:07
jap, das mit free ist klar ;)

aber wenn man's nicht vergisst...?

die Anschluss-Frage wäre auch:

int16_t * buf;

buf = (int16_t *) malloc(arrlen);
oder:
buf = (int16_t *) malloc(arrlen*sizeof(int16_t) );

... :?:

Mxt
28.06.2016, 15:15
jap, das mit free ist klar ;)
aber wenn man's nicht vergisst...?

Spätestens wenn mehrere an einem Programm arbeiten, wird es irgendwann wer vergessen.

Deshalb sieht es in modernem C++ jetzt so aus, statt


void f()
{
int* pi = new int;

// ...

delete pi;
}


nur noch


void f()
{
auto pi = std::make_unique<int>();

// ...
}

gelöscht wird automatisch, wenn irgendwo (auch durch eine exception) die Klammern verlassen werden.


Die Zusatzfrage überlasse ich dann mal den sicher bald erscheinenden C-Experten.

HaWe
28.06.2016, 15:21
C++ ist für mich definitiv keine Option, ich hasse Objekte wie der Teufel das Weihwasser... :mad:

a-Bär:

wenn eins hiervon richtig ist, wäre das schon mal gut zu wissen
(ich tippe aufs zweite...)


buf = (int16_t *) malloc(arrlen);
oder:
buf = (int16_t *) malloc(arrlen*sizeof(int16_t) );

Mxt
28.06.2016, 15:25
(ich tippe aufs zweite...)

Würde ich auch vermuten, aber wie gesagt ich bin kein C-Experte.

botty
28.06.2016, 15:51
Hallo HaWe,

wie Du getippt hast:


size_t len = 8;

int16_t *ptr = (int16_t*)malloc(arrlen * sizeof(int16_t));

/* Oder wenn's mit 0 initialisiert sein soll: *)
ptr = (int16_t*)calloc(arrlen, sizeof(int16_t));

ist die richtige Antwort.

Allerdings ist es dann üblich mittels Pointer Arithemetik und nicht mit dem Array-Operator auf das Feld zuzugreifen also drittes Element im Feld;


*(ptr + 2) = 32 * 27 + *(ptr + 1);

ist aber Geschmackssache.

HaWe
28.06.2016, 15:58
ok, super, danke - hatte ich beide getestet, Fehler besteht fort, dann liegt es nicht an malloc.

calloc kannte ich noch gar nicht, und es beantwortet eigtl sogar schon die nächste Frage, trotzdem, der Vollständigkeit halber:

wenn ich mit
buf = (int16_t *) malloc(arrlen*sizeof(int16_t) );
den array angelegt habe, wie kann ich ihn manuell (nachträglich) mit Null initialisieren?

Jetzt geht es also um memset...
ist das hier korrekt:
memset(buf, 0, sizeof(buf) );
...:?:

botty
28.06.2016, 16:20
Nein, so geht's nicht, genau darin unterscheiden sich Arrays mit vorgegebener Größe gegenüber Speicherblöcken die mittels malloc & Co angelegt werden.
sizeof(buf) gibt die Bytes für die Größe des Zeigers buf zurück. Der weiß nix über das was malloc & free so treiben.


memset(buf, 0, arrlen * sizeof(int16_t));

wäre korrekt.

Du mußt hier zwischen der Feldlänge die Du vorgibst und der Anzahl der Bytes, die sich immer aus 'Feldlänge mal Datenentypgröße' ergibt, unterscheiden.

HaWe
28.06.2016, 16:35
ahaaa... da könnte der seltsame Fehler begründet sein, das muss ich jetzt mal testen.


aber zu guter letzt, auch hier wieder die sizeof-Sache....:
Thema memcpy:

wenn ich einen array auf einen anderen kopieren will, habe ich bei statischen arrays verwendet
memcpy(array1, array2, sizeof(array2) );

nun ist array1 statisch und array2 ist dynamisch alloziert (mein buf).
geht das nun stattdessen so...
memcpy(array1, buf, arrlen * sizeof(int16_t) );
... :?:

botty
28.06.2016, 16:48
Solange sizeof(array1) <= arrlen * sizeof(int16_t) ist und array1 vom typ int16_t ist - ja.

HaWe
28.06.2016, 16:55
genau so ist es (beide arrays sind eigentlich gleich groß) -
perfekt, danke!

nun noch eine allerletzte Frage, ebenfalls wieder zu memset:

für den Fall, dass ich meinen dynamischen buf initialisieren will mit 128 statt mit 0, dann wäre doch vermutlich dieser Befehl dann auch richtig...

memset( buf, 128, arrlen*sizeof(int16_t) );

... :?:

Peter(TOO)
28.06.2016, 19:38
Hallo HaWe,


für den Fall, dass ich meinen dynamischen buf initialisieren will mit 128 statt mit 0, dann wäre doch vermutlich dieser Befehl dann auch richtig...

memset( buf, 128, arrlen*sizeof(int16_t) );
Richtig!

wie schon geschrieben wurde:
sizeof(Datentyp)
Gibt immer die Grösse in Bytes zurück. Dazu muss aber zur Compiltime die Grösse bekannt sein.
malloc(), memset(), mem...()
arbeiten ALLE mit der Byte-Anzahl, die haben keine Ahnung von Datentypen und deren Grösse.

calloc() macht die Multiplikation "sizeof() * n" selbst und ruft memset() mit 0 auf.

Grundsätzlich ist es egal ob du mit statischen Variablen oder welchen auf dem Heap arbeitest. Zeiger ist Zeiger, aber er muss auf einen gültigen Bereich zeigen!
Bei Variablen auf dem Heap bist du aber selbst verantwortlich, dass du nicht über den Rand der Variablen zugreifst, da kann dir der Compiler nicht helfen.

Daher kann man den Heap auch leicht zerschiessen.
Der Heap besteht meistens aus einer verketteten Liste. Die Informationen für die Verkettung liegt auf den Adressen vor dem Speicher, welcher dir malloc() liefert. Schreibst du hinter den dir zugesicherten Bereich, überschreibst du die Infos für die verkettete Liste des nächsten Blocks, dann kommt die Heap-Verwaltung durcheinander mit den lustigsten Effekten :-(

Unsauber an deinem Code ist noch, dass man den Rückgabewert von malloc() überprüfen sollte!
Wenn kein Platz auf den Heap ist, liefert malloc() NULL als Rückgabewert!


ptr = malloc(size);
if (ptr )
{
// du kannst mit ptr arbeiten
}
else
{
// Fehler: kein Platz auf dem Heap
}

zudem habe ich mir auch angewöhnt:


free(ptr);
ptr = NULL;


Meist macht zumindest die Debug-Version einen Test auf NULL-Pointer, somit bekommst du beim Debuggen eine Fehlermeldung.
Je nach CPU und deren Speicherverwaltung, löst ein Schreibzugriff mit einem NULL-Pointer einen Exception-Interrupt aus.

Wenn man einen NULL-Pointer mit free() ist garantiert, dass free() nichts macht.
Gibt man einen bereits zurückgegebenen Block nochmals mit free() zurück, oder man übergibt free() irgendeinen Wert, zerschiesst du meistens der Heap, auf alle Fälle sind das dann oft schwer zu findende Fehler :-(
Zur Laufzeit ist die Anordnung auf den Heap mehr oder weniger zufällig, weshalb sich solche Fehler oft auch nicht richtig reproduzieren lassen.

Ein häufiger Fehler ist auch, dass vergessen wird den Block mit free() an den Heap zurück zu geben. Dann geht dir irgendwann der freie Speicher aus und malloc() liefert NULL.

MfG Peter(TOO)

HaWe
28.06.2016, 20:10
super Tipps, vor allem das mit dem malloc Rückgabewert und dem Nullpointer nach free()!
Werde ich genau so machen, vielen Dank !

edit:
für

buf = (int16_t *)malloc(arrlen * sizeof (int16_t) );

heißt das dann
if(buf!=NULL) {...}

bzw.

if(buf==NULL) {
Serial.print("\n\n malloc() error - not enough memory\nprogram halted\n\n");
return;
}

richtig?

Peter(TOO)
28.06.2016, 20:39
super Tipps, vor allem das mit dem malloc Rückgabewert und dem Nullpointer nach free()!
Hat sich bei mir die letzten 25 Jahre so bewährt.


buf = (int16_t *)malloc(arrlen * sizeof (int16_t) );

heißt das dann
if(buf!=NULL) {...}

bzw.

if(buf==NULL) {
Serial.print("\n\n malloc() error - not enough memory\nprogram halted\n\n");
return;
}

richtig?
Jo!

Da für C alles !=0 als TRUE definiert ist, kann man auch verkürzt
if (buf) {...}
schreiben.

MfG Peter(TOO)

HaWe
28.06.2016, 20:43
perfekt, danke!

Sisor
28.06.2016, 20:44
Tipp: Um den Code lesbarer zu machen, Makros verwenden:
[Geschmacksache]

#define int16_malloc(len) (int16_t*)malloc(len*sizeof(int16_t))
#define delete(ptr) free(ptr);ptr=NULL

int main() {

int16_t* buf = int16_malloc(24); // 24 * int16 allokieren
if(buf) {
//... was mit buf anstellen
delete(buf);
}
...
}

Mxt
29.06.2016, 07:10
Vorsicht bei dem Namen "delete" für ein Makro.

Da HaWe und andere hier meist einen C++ Compiler benutzen um darin C zu machen. Das klein geschriebene delete ist ein reserviertes Wort in C++. Daher gibt es die Konvention Makros möglichst immer GROSS zu schreiben.

malloc und free sind in C++ eigentlich nur zur Rückwärtskompatibilität mit C vorhanden. Wenn man free auf einem Zeiger ausführt, der auf mit new angeforderten Speicher zeigt, oder andersrum aus Versehen delete auf mit malloc angeforderten Speicher aufruft, dann kann das Programm ganz plötzlich zuende sein.

Sisor
29.06.2016, 07:45
Das ist wohl richtig. Für c++ würde ich Makros auch nicht empfehlen.
Hier mal ein Auszug aus dem Arduino-Kern. Implementation von new und delete [new.cpp]:

#include <stdlib.h>

void *operator new(size_t size) {
return malloc(size);
}

void *operator new[](size_t size) {
return malloc(size);
}

void operator delete(void * ptr) {
free(ptr);
}

void operator delete[](void * ptr) {
free(ptr);
}

Mxt
29.06.2016, 08:16
Das sind spezielle Überladungen, die kommen bei normalem Anfordern von Speicher nicht zum Einsatz. Es handelt sich um sowas hier:
https://en.wikipedia.org/wiki/Placement_syntax

Man kann in C++ ja sowas machen


#include <new> // Must #include this to use "placement new"
#include "Fred.h" // Declaration of class Fred
void someCode()
{
char memory[sizeof(Fred)]; // Line #1
void* place = memory; // Line #2
Fred* f = new(place) Fred(); // Line #3 (see "DANGER" below)
// The pointers f and place will be equal
// ...
}

das ist eine andere Baustelle.


Im übrigen ist es meist so, dass new und delete auf malloc und free zurückgreifen, oder auf die Betriebssystemfunktionen die diesese beiden verwenden.

Aber new und delete rufen auch Konstruktor und Destruktor der angelegten Objekte auf. Ein malloc würde nur Speicher anfordern, das Objekt darin aber nicht initialisieren. Ein free auf etwas mit new angefordertem würde einem Objekt den Speicher klauen, läuft doch später noch der Destruktor knallt es, usw.

Sisor
29.06.2016, 08:25
Interessant, schaue ich mir demnächst mal genauer an...

Mxt
29.06.2016, 08:47
Wichtig für dich ist nur, dass du dann auch wirklich C machst. Also einen C-Compiler verwenden und mit *.c Codedateien arbeiten.

C++ ist (und war immer) nur eine Schnittmenge zu C, es ist kein größeres C. Es gibt Sachen, die sind in C und C++ anders.

HaWe
29.06.2016, 08:49
das geht leider nicht:
in Arduino habe ich gar keinen Einfluss drauf (ist immer gpp) und auf den Raspi portiert auch nicht immer (wenn Fremd-Libs C++ verwenden, muss ich auch g++ verwenden, ansonsten nutze ich gcc beim Pi ntl wo immer es geht.

Meinen eigenen Code schreibe ich immer nur in ANSI C Syntax, das ist einfach clean + straight sozusagen.

botty
29.06.2016, 10:14
HaWe, das Du nur C++ in der Arduino IDE benutzen kannst ist nur bedingt richtig.
Der Sketch wird durch den eigenen Präprozessor gejagt und dann das CPP-File durch den (kastrierten) g++.
Wenn Du aber eine "*.c"-Datei dem Projekt hinzufügst, dann wird diese mit dem gcc und nicht dem g++ kompiliert. Bei *.cc *.cpp vice versa.
Dementsprechend müssen dann die Header "extern "C" { ... }" oder auch nicht enthalten, sonst produziert der Linker freudig Fehlermeldungen.

Gruß

HaWe
29.06.2016, 10:38
aha, wieder etwas schlauer geworden - so in die Tiefen und Untiefen der Arduino-IDE bin ich gar nicht vorgedrungen, ich nutze einfach die Standard-Libs (egal welcher Code, ich guck auch normalerweise nicht nach C oder C++ da drin - viele verwenden allerdings C++, das sieht man ja schon an Serial, Wire und SPI.
Auch .c Dateien schließe ich nicht ein, sondern ganz einfach nur 1 .ino Code - und soweit es mein eigener ist, verwende ich hier kein C++, sonden nur C code (plus Serial.print etc, Wire.begin etc., und die ganzen anderen unvermeidbaren automatisch eigebundenen Sketch-Klassen).

Peter(TOO)
29.06.2016, 21:44
Wenn Du aber eine "*.c"-Datei dem Projekt hinzufügst, dann wird diese mit dem gcc und nicht dem g++ kompiliert. Bei *.cc *.cpp vice versa.
Dementsprechend müssen dann die Header "extern "C" { ... }" oder auch nicht enthalten, sonst produziert der Linker freudig Fehlermeldungen.
Ein Unterschied zwischen C und C++ liegt darin, dass C die Namen für Funktionen direkt aus dem Quelltext übernimmt.
C++ erzeugt decoratet Labels, an den Funktionsnamen werden einfach noch Buchstaben angehängt, welche die Aufruf-Parameter angeben. Dadurch werden überladene Funktionen für den Linker unterschiedlich. Man braucht also keinen speziellen Linker für C++.

Zudem war C++ ursprünglich eigentlich nur eine Art Preprozessor. Ein C++-Programm wurde in ein C-Programm umgesetzt, welches dann mit dem C-Compiler übersetzt wurde.

MfG Peter(TOO)

Wsk8
29.06.2016, 22:16
genau so ist es (beide arrays sind eigentlich gleich groß) -
perfekt, danke!

nun noch eine allerletzte Frage, ebenfalls wieder zu memset:

für den Fall, dass ich meinen dynamischen buf initialisieren will mit 128 statt mit 0, dann wäre doch vermutlich dieser Befehl dann auch richtig...

memset( buf, 128, arrlen*sizeof(int16_t) );

... :?:
Nur wenn du in JEDES Byte 128 schreiben willst. Wenn du in jedes Feld (2 Byte <-> int16) 128 schreiben willst, funktioniert das nicht mehr!

Und der Rückgabewert von malloc() wird für gewöhnlich nicht gecastet. http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc

mfg

HaWe
30.06.2016, 07:55
stimmt, ich meinte eigentlich: in jedes int-Feld 128 reinschreiben -
ich war der Meinung durch den Parameter
arrlen*sizeof(int16_t)
würde memset automatisch "arrlen" mal den Wert 128 einfügen.
Gibt es denn eine schnelle Methode, alle Felder mit 128 zu füllen, außer einzeln, iterativ?

mit dem gecastet meintest du doch
(int16_t *) malloc ....
oder?
das hatte ich aus einer malloc Funktions-Definition ...:
http://www.cplusplus.com/reference/cstdlib/malloc/
(siehe Anwendungsbeispiel weiter unten mit (char *)malloc ! )

botty
30.06.2016, 09:30
Zum Cast im Kontext von einem Arduino Sketch ist die Aussage

Und der Rückgabewert von malloc() wird für gewöhnlich nicht gecastet.
schlichtweg falsch. In C++ ist ein impliziter Cast wie


int* buf;
void *ptr;

buf = ptr;

nicht erlaubt (malloc gibt einen void* zurück). Und da der Sketch C++ Code ist, auch wenn Du im C Stil programmierst, ist das ein Fehler.

Wenn Du wirklich einen Wert in den Feldern stehen haben willst, mußt Du sie über eine Schleife initialisieren. Da führt kein Weg dran vorbei.

HaWe
30.06.2016, 14:08
danke, mach ich so!