Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
Vorwort des Gutachters
1 Einstieg in C
2 Das erste Programm
3 Grundlagen
4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()«
5 Basisdatentypen
6 Operatoren
7 Typumwandlung
8 Kontrollstrukturen
9 Funktionen
10 Präprozessor-Direktiven
11 Arrays
12 Zeiger (Pointer)
13 Kommandozeilenargumente
14 Dynamische Speicherverwaltung
15 Strukturen
16 Ein-/Ausgabe-Funktionen
17 Attribute von Dateien und das Arbeiten mit Verzeichnissen (nicht ANSI C)
18 Arbeiten mit variabel langen Argumentlisten – <stdarg.h>
19 Zeitroutinen
20 Weitere Headerdateien und ihre Funktionen (ANSI C)
21 Dynamische Datenstrukturen
22 Algorithmen
23 CGI mit C
24 MySQL und C
25 Netzwerkprogrammierung und Cross–Plattform-Entwicklung
26 Paralleles Rechnen
27 Sicheres Programmieren
28 Wie geht’s jetzt weiter?
A Operatoren
B Die C-Standard-Bibliothek
Stichwort

Download:
- ZIP, ca. 10,6 MB
Buch bestellen
Ihre Meinung?

Spacer
<< zurück
C von A bis Z von Jürgen Wolf
Das umfassende Handbuch
Buch: C von A bis Z

C von A bis Z
3., aktualisierte und erweiterte Auflage, geb., mit CD und Referenzkarte
1.190 S., 39,90 Euro
Galileo Computing
ISBN 978-3-8362-1411-7
Pfeil 16 Ein-/Ausgabe-Funktionen
Pfeil 16.1 Was ist eine Datei?
Pfeil 16.2 Formatierte und unformatierte Ein-/Ausgabe
Pfeil 16.3 Standard-Streams
Pfeil 16.4 Höhere Ein-/Ausgabe-Funktionen
Pfeil 16.5 Datei (Stream) öffnen – »fopen«
Pfeil 16.5.1 Modus für »fopen()«
Pfeil 16.5.2 Maximale Anzahl geöffneter Dateien – »FOPEN_MAX«
Pfeil 16.6 Zeichenweise lesen und schreiben – »getchar()« und »putchar()«
Pfeil 16.6.1 Ein etwas portableres »getch()«
Pfeil 16.7 Zeichenweise lesen und schreiben – »putc()«/»fputc()« und »getc()«/»fgetc()«
Pfeil 16.8 Datei (Stream) schließen – »fclose()«
Pfeil 16.9 Formatiertes Einlesen/Ausgeben von Streams mit »fprintf()« und »fscanf()«
Pfeil 16.10 Standard-Streams in C
Pfeil 16.10.1 Standard-Streams umleiten
Pfeil 16.11 Fehlerbehandlung von Streams – »feof()«, »ferror()« und »clearerr()«
Pfeil 16.12 Gelesenes Zeichen in die Eingabe zurückschieben – »ungetc()«
Pfeil 16.13 (Tastatur-)Puffer leeren – »fflush()«
Pfeil 16.14 Stream positionieren – »fseek()«, »rewind()« und »ftell()«
Pfeil 16.15 Stream positionieren – »fsetpos()«, »fgetpos()«
Pfeil 16.16 Zeilenweise Ein-/Ausgabe von Streams
Pfeil 16.16.1 Zeilenweise lesen mit »gets()«/»fgets()«
Pfeil 16.16.2 Zeilenweise schreiben mit »puts()«/»fputs()«
Pfeil 16.16.3 Zeilenweise vom Stream einlesen mit »getline()« (nicht ANSI C)
Pfeil 16.16.4 Rezepte für zeilenweises Einlesen und Ausgeben
Pfeil 16.17 Blockweise lesen und schreiben – »fread()« und »fwrite()«
Pfeil 16.17.1 Blockweise lesen – »fread()«
Pfeil 16.17.2 Blockweise schreiben – »fwrite()«
Pfeil 16.17.3 Big Endian und Little Endian
Pfeil 16.18 Datei (Stream) erneut öffnen – »freopen()«
Pfeil 16.19 Datei löschen oder umbenennen – »remove()« und »rename()«
Pfeil 16.19.1 remove()
Pfeil 16.19.2 rename()
Pfeil 16.20 Pufferung einstellen – »setbuf()« und »setvbuf()«
Pfeil 16.20.1 Die Funktion »setbuf()«
Pfeil 16.20.2 Die Funktion »setvbuf()«
Pfeil 16.21 Temporäre Dateien erzeugen – »tmpfile()« und »tmpnam()«
Pfeil 16.21.1 »mkstemp()« – sichere Alternative für Linux/UNIX (nicht ANSI C)
Pfeil 16.22 Fehlerbehandlung
Pfeil 16.22.1 Fehlerausgabe mit »perror()«
Pfeil 16.22.2 Fehlerausgabe mit »strerror()«
Pfeil 16.23 Formatiert in einen String schreiben und formatiert aus einem String lesen – »sscanf()« und »sprintf()«
Pfeil 16.24 Byte- und wide-orientierter Stream
Pfeil 16.25 Ein fortgeschrittenes Thema
Pfeil 16.26 Low-Level-Datei-I/O-Funktionen (nicht ANSI C)
Pfeil 16.26.1 Datei öffnen – »open()«
Pfeil 16.26.2 Datei schließen – »close()«
Pfeil 16.26.3 Datei erzeugen – »creat()«
Pfeil 16.26.4 Schreiben und Lesen – »write()« und »read()«
Pfeil 16.26.5 File-Deskriptor positionieren – »lseek()«
Pfeil 16.26.6 File-Deskriptor von einem Stream – »fileno()«
Pfeil 16.26.7 Stream von File-Deskriptor – »fdopen()«


Galileo Computing - Zum Seitenanfang

16.17 Blockweise lesen und schreiben – »fread()« und »fwrite()« Zur nächsten ÜberschriftZur vorigen Überschrift

Diese beiden Funktionen lassen sich nicht so recht in ein Thema der Datei-E/A einordnen – weder in den höheren Standardfunktionen (High Level) noch in den niedrigeren Funktionen (Low-Level). Mit fread() und fwrite() wird eine Datei nicht als strukturierte Textdatei und auch nicht als unformatierter Bytestrom betrachtet. Die Dateien werden im Binärmodus bearbeitet und haben eine feste Satzstruktur. Das heißt, die Funktionen fread() und fwrite() tun nichts anderes, als ganze Blöcke binär zu lesen und zu schreiben.


Hinweis

Die Bezeichnungen Textmodus und Binärmodus können hierbei recht verwirrend sein. ANSI C erlaubt beide Möglichkeiten, eine Datei zu öffnen. Informationen können somit im Binärmodus oder Textmodus geschrieben oder gelesen werden. Eine Textdatei beispielsweise können Sie also im Binärmodus öffnen. Ebenso lässt sich ein Text im Binärmodus abspeichern. Sie können auch die Funktion fgetc() oder getc() verwenden, um eine Datei mit binären Daten zu kopieren.


Das Gute an diesen beiden Funktionen ist ihre einfache Anwendung. Der Nachteil ist aber, dass die Daten der Datei, die fwrite() schreibt, nicht portabel, sondern plattformabhängig sind. Wollen Sie zum Beispiel mit diesen Funktionen Hauptspeicherinhalte direkt in eine Datei schreiben, könnten aufgrund eines anderen Alignments Probleme auftreten. Dies kommt daher, weil ein Member-Alignment in anderen Strukturen eine andere Byte-Reihenfolge bei Ganzzahlen oder eine unterschiedliche interne Darstellung von Fließkommazahlen haben könnte. Dieses Problem kann schon bei unterschiedlichen Compilern auftreten! Ist es also wichtig, dass die Daten auch auf anderen Systemen gelesen werden können, haben Sie folgende zwei Möglichkeiten:

  • Die bessere Lösung wäre es, eine einfache ASCII-Textdatei zu verwenden, die mit fprintf() geschrieben und mit fscanf() gelesen wird. Ähnlich wird übrigens auch bei Netzwerkprotokollen vorgegangen. Zwar sind Textdateien meistens größer, und die Ein-/Ausgabe läuft ein wenig langsamer ab, aber der Vorteil, die einfachere Handhabung der Daten, macht diese Nachteile wieder wett.
  • Sie überprüfen, ob es sich dabei um eine Big-Endian- oder Little-Endian-Maschine handelt.

Diese Funktionen ergänzen das Sortiment der Lese- und Schreibfunktionen hervorragend. Denn mit fgets() und fputs() lassen sich, wegen der besonderen Bedeutung der Zeichen '\0' und '\n' in ihnen, schlecht ganze Blöcke von Daten lesen bzw. schreiben. Ebenso ist es nicht sinnvoll, Daten Zeichen für Zeichen zu verarbeiten, wie dies bei den Funktionen fputc() und fgetc() geschieht. Außerdem ist es naheliegend, dass sich diese beiden Funktionen hervorragend zum Lesen und Schreiben von Strukturen eignen.


Hinweis

Da fread() und fwrite() nicht mit Zeichen bzw. Strings arbeiten, gibt es keine Funktion für breite Zeichen zu ihnen.



Galileo Computing - Zum Seitenanfang

16.17.1 Blockweise lesen – »fread()« Zur nächsten ÜberschriftZur vorigen Überschrift

Die Syntax der Funktion lautet wie folgt:

size_t fread(void *puffer, size_t blockgroesse,
             size_t blockzahl, FILE *datei);

fread() liest blockzahl-Speicherobjekte, von denen jedes die Größe von blockgroesse Bytes hat, aus dem Stream datei, der zuvor geöffnet wurde, in die Adresse von puffer. Für puffer muss dementsprechend viel Platz zur Verfügung stehen. Der Rückgabewert ist die Anzahl der gelesenen Speicherobjekte. Ist die Anzahl kleiner als blockzahl, wurde entweder das Dateiende erreicht, oder es trat ein Fehler auf.


Hinweis

size_t ist ein primitiver Systemdatentyp, der in der Headerdatei <stddef.h> definiert ist. Der maximale Wert des Typ ist implementierungsabhängig. Meistens ist dieser Typ allerdings als unsigned int implementiert).


Abbildung 16.6 Blockweises Lesen mit »fread()«

Nehmen wir an, wir haben eine Datei namens wert.dat, deren Inhalte aus Integerwerten bestehen. Werden z. B. die ersten zehn Werte benötigt, sieht der Quellcode folgendermaßen aus:

/* fread.c */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
   int puffer[10];
   FILE *quelle;
   int i;

   quelle = fopen("wert.dat", "r+b");
   if(quelle != NULL)
      fread(&puffer, sizeof(int), 10, quelle);

   for(i = 0; i < 10; i++)
      printf("Wert %d = %d\n", i, puffer[i]);
 return EXIT_SUCCESS;
}

Folgende Zeile soll wie oben interpretiert werden:

fread(&puffer, sizeof(int), 10, quelle);

fread() liest 10 Datenobjekte mit der Größe von je sizeof(int) Bytes aus dem Stream quelle in die Adresse von puffer.

Ein wenig undurchsichtig dürfte der Parameter void *puffer bei fread() und fwrite() erscheinen. Mit dem void-Zeiger haben Sie den Vorteil, dass diesem Parameter ein Zeiger beliebigen Datentyps übergeben werden kann. In Kapitel 12, »Zeiger (Pointer)«, haben wir dies bereits durchgenommen.


Galileo Computing - Zum Seitenanfang

16.17.2 Blockweise schreiben – »fwrite()« Zur nächsten ÜberschriftZur vorigen Überschrift

Kommen wir jetzt zur Funktion fwrite():

size_t fwrite(const void *puffer,size_t blockgroesse,
              size_t blockzahl, FILE *datei);

Mit fwrite() werden blockzahl-Speicherobjekte, von denen jedes blockgroesse Bytes groß ist, von der Adresse puffer in den Ausgabe-Stream datei geschrieben. Der Rückgabewert ist auch hier die Anzahl der geschriebenen Speicherobjekte. Ist die Anzahl kleiner als blockzahl, dann ist ein Fehler aufgetreten.

Abbildung 16.7 Blockweise schreiben mit »fwrite()«

Wieder ein Beispiel:

struct {
   char name[20];
   char vornam[20];
   char wohnort[30];
   int alter;
   int plz;
   char Strasse[30];
} adressen;

FILE *quelle;

strcpy(adressen.name, "Barack");
strcpy(adressen.vornam, "Obama");
strcpy(adressen.wohnort, "Washington D.C");
adressen.alter = 55;
adressen.plz = 23223;
...
if((quelle=fopen("adres.dat", "w+b")) == NULL)
...
fwrite(&adressen, sizeof(struct adressen), 1, quelle);

Hier wird mit fwrite() aus der Adresse adressen ein Speicherobjekt mit der Größe von sizeof(struct adressen) Bytes in den Stream quelle geschrieben.

Als Beispiel zu den Funktionen fread() und fwrite() folgt ein kleines Adressenverwaltungsprogramm ohne irgendwelche besonderen Funktionen, um nicht vom eigentlichen Thema abzulenken:

/* fread_fwrite.c */
#include <stdio.h>
#include <stdlib.h>

struct {
   char vorname[20];
   char nachname[30];
   char strasse[30];
   char hausnummer[5];
   char plz[7];
   char ort[30];
   char sternzeichen[30];
   char alter[3];
} adressen;

void speichern(void) {
   FILE *save = fopen("adressen.dat","r+b");
   if( NULL == save ) {
      save = fopen("adressen.dat","w+b");
      if( NULL == save ) {
         fprintf(stderr,"Kann \"adressen.dat\" nicht öffnen!\n");
         return;
      }
   }
   /* FILE-Zeiger save auf das Ende der Datei setzen */
   fseek(save, 0, SEEK_END);

   /* Wir schreiben eine Adresse ans Ende von "adressen.dat". */
   if(fwrite(&adressen, sizeof(adressen), 1, save) != 1) {
      fprintf(stderr, "Fehler bei fwrite...!!!\n");
      return;
   }
   /* Wir geben unseren FILE-Zeiger wieder frei. */
   fclose(save);
}

void ausgabe(void) {
   FILE *output = fopen("adressen.dat","r+b");
   if( NULL == output ) {
      fprintf(stderr,"Kann \"adressen.dat\" nicht öffnen!\n");
      return;
   }
   /* Wir lesen alle Adressen aus "adressen.dat". */
   while(fread(&adressen, sizeof(adressen), 1, output) == 1) {
      printf("Vorname...........: %s",adressen.vorname);
      printf("Nachname..........: %s",adressen.nachname);
      printf("Strasse...........: %s",adressen.strasse);
      printf("Hausnummer........: %s",adressen.hausnummer);
      printf("Postleitzahl......: %s",adressen.plz);
      printf("Ort...............: %s",adressen.ort);
      printf("Sternzeichen......: %s",adressen.sternzeichen);
      printf("Alter.............: %s",adressen.alter);
      printf("\n\n");
   }
   fclose(output);
}
void eingabe(void) {
   printf("Vorname...........:");
   fgets(adressen.vorname, sizeof(adressen.vorname), stdin);
   printf("Nachname..........:");
   fgets(adressen.nachname, sizeof(adressen.nachname),stdin);
   printf("Strasse...........:");
   fgets(adressen.strasse, sizeof(adressen.strasse), stdin);
   printf("Hausnummer........:");
   fgets(adressen.hausnummer,sizeof(adressen.hausnummer),stdin);
   printf("Postleitzahl......:");
   fgets(adressen.plz, sizeof(adressen.plz), stdin);
   printf("Ort...............:");
   fgets(adressen.ort, sizeof(adressen.ort), stdin);
   printf("Sternzeichen......:");
   fgets(adressen.sternzeichen,sizeof(adressen.sternzeichen),
      stdin );
   printf("Alter.............:");
   fgets(adressen.alter, sizeof(adressen.alter), stdin);
   speichern();
}

int main(void) {
   int wahl;

   do {
      printf("Was wollen Sie machen:\n\n");
      printf("-1- Neuen Datensatz hinzufuegen\n");
      printf("-2- Alle Datensaetze ausgeben\n");
      printf("-3- Programm beenden\n\n");
      printf("Ihre Auswahl : ");
      do {
         scanf("%d",&wahl);
      } while(getchar() != '\n');
      switch(wahl) {
         case 1 : eingabe();        break;
         case 2 : ausgabe();        break;
         case 3 : printf("...Programm wird beendet\n");
                  break;
         default: printf(">>%d<< ???\n",wahl);
      }
   } while(wahl != 3);
   return;
}

Zuerst wurde eine Struktur mit dem Namen adressen deklariert. In der Funktion speichern() wird, falls vorhanden, die Datei adressen.dat geöffnet. Ansonsten wird diese Datei erstellt:

   FILE *save = fopen("adressen.dat","r+b");
   if( NULL == save ) {
      save = fopen("adressen.dat","w+b");
      if( NULL == save ) {

Gleich darauf wird der Stream save an das Ende der Datei adressen.dat positioniert:

fseek(save, 0, SEEK_END);

Jetzt kann der Adressensatz in die Datei geschrieben werden:

if(fwrite(&adressen, sizeof(adressen), 1, save) != 1)

Nochmals eine Erklärung von fwrite():

  • &adressen – Anfangsadresse der Struktur adressen, welche am Programmbeginn deklariert und in der Funktion eingabe() mit Werten initialisiert wurde
  • sizeof(struct adressen) – Größe (Blockgröße) in Byte, die vorgibt, wie viel auf einmal in den Stream save geschrieben werden soll
  • 1 – Anzahl der Blöcke von der Größe sizeof(adressen), die in den Stream save geschrieben werden
  • save – Stream, der zuvor geöffnet wurde, und in den geschrieben wird

Diese Anweisung wurde in eine if-Bedingung gepackt, die eine Fehlerausgabe vornimmt, falls weniger als ein Block geschrieben wird. Mit der Funktion ausgabe() wird diese Datei jetzt über fread() blockweise ausgelesen und der Inhalt auf dem Bildschirm ausgegeben:

while(fread(&adressen, sizeof(adressen), 1, output) == 1)

Es wird so lange ausgelesen, bis kein ganzer Block der Größe sizeof(adressen) mehr vorhanden ist. Auch hierzu bekommen Sie eine genauere Erläuterung von fread():

  • &adressen – Hierhin wird der Block der Größe sizeof(adressen), auf die der FILE-Zeiger output zeigt, »geschoben«. Natürlich handelt es sich auch hier um die Struktur adressen, die durch den Aufruf von fread() mit dementsprechenden Werten initialisiert wird.
  • sizeof(adressen) – Größe des Blocks, der gelesen werden soll
  • 1 – Anzahl der Blöcke, die gelesen werden
  • output – Stream, aus dem gelesen wird

Galileo Computing - Zum Seitenanfang

16.17.3 Big Endian und Little Endian topZur vorigen Überschrift

Ich habe bereits erwähnt, dass die Funktionen fread() und fwrite() nicht portabel und somit plattformabhängig sind. Sollten Sie also Programme schreiben wollen, die auf den verschiedensten Systemen laufen sollen, bleibt Ihnen nur die Wahl, diese Funktionen nicht zu verwenden, oder Sie finden heraus, auf welchem System genau sie laufen sollen. Unterschieden werden die Systeme dabei nach Little Endian und Big Endian. Little Endian und Big Endian sind zwei Methoden, wie die einzelnen Bytes im Speicher angeordnet sind. Little Endian und Big Endian unterscheiden sich durch die Anordnung des most significant byte und des least significant byte. Bei einer Word-Größe der CPU von vier Bytes wird das rechte Ende als least significant byte und das linke Ende als most significant byte bezeichnet. Das least significant byte stellt dabei die niedrigeren Werte und das most significant byte die größeren Werte in einem Word dar. Als Beispiel dient jetzt folgende Hex-Zahl:

22CCDDEE

Auf den unterschiedlichen Systemen wird diese Hex-Zahl im Speicher folgendermaßen abgelegt:


Tabelle 16.7 Little Endian und Big Endian im Vergleich

Adresse 0x12345 0x12346 0x12347 0x12348

Big Endian

22
CC
DD
EE

Little Endian

EE
DD
CC
22

Um jetzt herauszufinden, auf was für einem System das Programm ausgeführt wird, müssen Sie diese Hex-Zahl in einen Speicher schreiben, der Platz für eine Wortbreite bietet und das erste Byte mithilfe von Bit-Operationen überprüfen. Hier sehen Sie das Listing dazu:

/* endian.c */
#include <stdio.h>
#include <stdlib.h>
typedef unsigned int  WORD;
typedef unsigned char BYTE;

int main(void) {
   /* Word in den Speicher schreiben */
   WORD Word = 0x22CCDDEE;
   /* Zeiger auf ein Byte */
   BYTE *Byte;

   /* Word-Zeiger auf Byte-Zeiger casten */
   Byte = (BYTE *) &Word;


/* Speicherinhalt nach Adressen von links nach rechts
 * ausgeben.
 * byte[0]byte[1]byte[2]byte[3]
 * 22     CC     DD     EE      Speicherinhalt bei Little Endian
 * EE     DD     CC     22      Speicherinhalt bei Big Endian
 */
   /* Ist Byte[0] == 11 */
   if(Byte[0] == ((Word >>  0) & 0xFF))
      printf("Little Endian Architecture\n");
   /* oder ist Byte[0] == CC */
   if(Byte[0] == ((Word >> 24) & 0xFF))
      printf("Big Endian Architecture\n");
   return EXIT_SUCCESS;
}

Mit

if(Byte[0] == ((Word >>  0) & 0xFF))

werden die ersten acht Bits (ein Byte) mithilfe einer Maske (FF == 256 == 1 Byte) gezielt getestet. Werden bei dem Ausdruck ((Word >> 0) & 0xFF)) praktisch keine Bits auf 0 gesetzt und stimmt danach der ausgewertete Ausdruck mit Byte[0] überein, haben Sie ein Little-Endian-System. Bei der zweiten Bedingung ist es dasselbe, nur wird dabei das vierte Byte (d. h. das 24. bis 32. Bit) verwendet. Zu den Little-Endian-Systemen gehören z. B.:

  • Intel-CPUs
  • DEC Alpha
  • VAX

Big-Endian-Systeme sind zum Beispiel:

  • Motorola MC68000 (Amiga, Atari)
  • SPARC CPUs (SUN)
  • IBM PowerPC

Einen faden Nachgeschmack hat diese Methode allerdings dann doch. Jetzt wissen Sie zwar, ob es sich um ein Little- oder Big-Endian-System handelt, aber nun müssen Sie sich dennoch selbst darum kümmern, dass die einzelnen Bytes richtig gelesen und geschrieben werden. Damit ist gemeint, dass Sie die Bits selbst verschieben müssen. Aber dies ist ein Thema, das den Rahmen dieses Buchs sprengen würde.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.






<< zurück
  
  Zum Katalog
Zum Katalog: C von A bis Z

 C von A bis Z
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Einstieg in C++






 Einstieg in C++


Zum Katalog: C++ von A bis Z






 C++ von A bis Z


Zum Katalog: C/C++






 C/C++


Zum Katalog: Shell-Programmierung






 Shell-Programmierung


Zum Katalog: Linux-UNIX-Programmierung






 Linux-UNIX-
 Programmierung


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Katalog: Ubuntu GNU/Linux






 Ubuntu GNU/Linux


Zum Katalog: Coding for Fun







 Coding for Fun 


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Galileo Press 2009
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de