Les tableaux circulaires (ou buffers circulaires) sont des structures de données puissantes souvent utilisées pour gérer des flux continus de données, comme les fichiers. Ce guide explore la création, la gestion, et l’utilisation de fichiers avec des tableaux circulaires en C.
Un tableau circulaire est un tableau où les données sont organisées de manière circulaire. Une fois que la fin du tableau est atteinte, l’insertion ou la lecture recommence au début du tableau.
Un tableau circulaire nécessite :
head
) et d’écriture (tail
).typedef struct {
char *buffer; // Tableau pour stocker les données
int capacity; // Capacité maximale du tableau
int head; // Indice de lecture
int tail; // Indice d'écriture
int size; // Nombre d'éléments dans le tableau
} CircularBuffer;
Initialisez un tableau circulaire avec une capacité donnée.
#include <stdio.h>
#include <stdlib.h>
void initializeBuffer(CircularBuffer *cb, int capacity) {
cb->buffer = (char *)malloc(capacity * sizeof(char));
if (cb->buffer == NULL) {
printf("Erreur d'allocation mémoire.\n");
exit(1);
}
cb->capacity = capacity;
cb->head = 0;
cb->tail = 0;
cb->size = 0;
}
Ajoutez des données au tableau circulaire.
int addToBuffer(CircularBuffer *cb, char data) {
if (cb->size == cb->capacity) {
return -1; // Le buffer est plein
}
cb->buffer[cb->tail] = data;
cb->tail = (cb->tail + 1) % cb->capacity; // Avance en mode circulaire
cb->size++;
return 0;
}
Lisez des données du tableau circulaire.
int readFromBuffer(CircularBuffer *cb, char *data) {
if (cb->size == 0) {
return -1; // Le buffer est vide
}
*data = cb->buffer[cb->head];
cb->head = (cb->head + 1) % cb->capacity; // Avance en mode circulaire
cb->size--;
return 0;
}
Libérez la mémoire allouée.
void freeBuffer(CircularBuffer *cb) {
free(cb->buffer);
cb->buffer = NULL;
cb->capacity = cb->head = cb->tail = cb->size = 0;
}
Utilisez un tableau circulaire pour lire un fichier en blocs.
#include <stdio.h>
#include <stdlib.h>
void readFileWithBuffer(const char *filename, CircularBuffer *cb) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
printf("Erreur d'ouverture du fichier.\n");
return;
}
char ch;
while ((ch = fgetc(file)) != EOF) {
if (addToBuffer(cb, ch) == -1) {
printf("Buffer plein. Traitez ou videz le buffer.\n");
break;
}
}
fclose(file);
}
Écrivez les données du tableau circulaire dans un fichier.
void writeBufferToFile(const char *filename, CircularBuffer *cb) {
FILE *file = fopen(filename, "w");
if (file == NULL) {
printf("Erreur d'ouverture du fichier pour écriture.\n");
return;
}
char ch;
while (readFromBuffer(cb, &ch) == 0) {
fputc(ch, file);
}
fclose(file);
}
Un programme complet pour lire un fichier, stocker les données dans un tableau circulaire, puis écrire ces données dans un autre fichier.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char *buffer;
int capacity;
int head;
int tail;
int size;
} CircularBuffer;
void initializeBuffer(CircularBuffer *cb, int capacity);
int addToBuffer(CircularBuffer *cb, char data);
int readFromBuffer(CircularBuffer *cb, char *data);
void freeBuffer(CircularBuffer *cb);
void readFileWithBuffer(const char *filename, CircularBuffer *cb);
void writeBufferToFile(const char *filename, CircularBuffer *cb);
int main() {
CircularBuffer cb;
initializeBuffer(&cb, 10); // Taille du tableau circulaire
readFileWithBuffer("input.txt", &cb); // Lire les données du fichier
writeBufferToFile("output.txt", &cb); // Écrire les données dans un fichier
freeBuffer(&cb); // Libérer la mémoire
return 0;
}
void initializeBuffer(CircularBuffer *cb, int capacity) {
cb->buffer = (char *)malloc(capacity * sizeof(char));
if (cb->buffer == NULL) {
printf("Erreur d'allocation mémoire.\n");
exit(1);
}
cb->capacity = capacity;
cb->head = 0;
cb->tail = 0;
cb->size = 0;
}
int addToBuffer(CircularBuffer *cb, char data) {
if (cb->size == cb->capacity) {
return -1; // Le buffer est plein
}
cb->buffer[cb->tail] = data;
cb->tail = (cb->tail + 1) % cb->capacity;
cb->size++;
return 0;
}
int readFromBuffer(CircularBuffer *cb, char *data) {
if (cb->size == 0) {
return -1; // Le buffer est vide
}
*data = cb->buffer[cb->head];
cb->head = (cb->head + 1) % cb->capacity;
cb->size--;
return 0;
}
void freeBuffer(CircularBuffer *cb) {
free(cb->buffer);
cb->buffer = NULL;
cb->capacity = cb->head = cb->tail = cb->size = 0;
}
void readFileWithBuffer(const char *filename, CircularBuffer *cb) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
printf("Erreur d'ouverture du fichier.\n");
return;
}
char ch;
while ((ch = fgetc(file)) != EOF) {
if (addToBuffer(cb, ch) == -1) {
printf("Buffer plein. Traitez ou videz le buffer.\n");
break;
}
}
fclose(file);
}
void writeBufferToFile(const char *filename, CircularBuffer *cb) {
FILE *file = fopen(filename, "w");
if (file == NULL) {
printf("Erreur d'ouverture du fichier pour écriture.\n");
return;
}
char ch;
while (readFromBuffer(cb, &ch) == 0) {
fputc(ch, file);
}
fclose(file);
}
L’optimisation des tampons circulaires implique d’améliorer leurs performances, de réduire la surcharge et de garantir une utilisation efficace de la mémoire. Voici des stratégies et des techniques pour optimiser les tampons circulaires :
index % size
), vous pouvez utiliser des opérations au niveau du bit, qui sont plus rapides. define BUFFER_SIZE 1024 // Must be a power of two #define MASK (BUFFER_SIZE - 1) int index = (current_index + 1) & MASK; // Faster than modulo
Pour les environnements multithread :
head
les tail
mises à jour.Exemple (tampon SPSC sans verrouillage) :
#include <stdatomic.h>
typedef struct {
char *buffer;
atomic_int head;
atomic_int tail;
int capacity;
} CircularBuffer;
int enqueue(CircularBuffer *cb, char data) {
int next_tail = (atomic_load(&cb->tail) + 1) % cb->capacity;
if (next_tail == atomic_load(&cb->head)) {
return -1; // Buffer is full
}
cb->buffer[cb->tail] = data;
atomic_store(&cb->tail, next_tail);
return 0;
}
int dequeue(CircularBuffer *cb, char *data) {
if (atomic_load(&cb->head) == atomic_load(&cb->tail)) {
return -1; // Buffer is empty
}
*data = cb->buffer[cb->head];
atomic_store(&cb->head, (cb->head + 1) % cb->capacity);
return 0;
}
malloc
et free
.char __attribute__((aligned(64))) buffer[BUFFER_SIZE];
Remplacez les conditions dans les opérations de lecture/écriture par des limites ou des indicateurs précalculés. Cela réduit les ramifications et améliore les performances dans les systèmes critiques en termes de temps.
Exemple (éviter les conditions pour les vérifications pleines/vides) :
#define IS_EMPTY(cb) ((cb)->head == (cb)->tail)
#define IS_FULL(cb) (((cb)->tail + 1) % (cb)->capacity == (cb)->head)
Exemple:
cCopier le code#define HIGH_WATERMARK (BUFFER_SIZE * 0.75)
#define LOW_WATERMARK (BUFFER_SIZE * 0.25)
if (buffer_size > HIGH_WATERMARK) {
// Slow down producer
}
if (buffer_size < LOW_WATERMARK) {
// Notify producer to speed up
}
Pour des scénarios tels que le traitement audio ou vidéo :
Exemple:
static inline int isBufferEmpty(const CircularBuffer *cb) {
return cb->head == cb->tail;
}
gprof
, valgrind
ou des compteurs de performances matérielles pour identifier les goulots d’étranglement.Vous trouverez ci-dessous une implémentation entièrement optimisée utilisant des tailles de puissance de deux, une conception sans verrouillage et une gestion efficace de la mémoire.
include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#define BUFFER_SIZE 1024 // Power of two
#define MASK (BUFFER_SIZE - 1)
typedef struct {
char buffer[BUFFER_SIZE];
atomic_int head;
atomic_int tail;
} CircularBuffer;
void initializeBuffer(CircularBuffer *cb) {
atomic_store(&cb->head, 0);
atomic_store(&cb->tail, 0);
}
int enqueue(CircularBuffer *cb, char data) {
int current_tail = atomic_load(&cb->tail);
int next_tail = (current_tail + 1) & MASK;
if (next_tail == atomic_load(&cb->head)) {
return -1; // Buffer is full
}
cb->buffer[current_tail] = data;
atomic_store(&cb->tail, next_tail);
return 0;
}
int dequeue(CircularBuffer *cb, char *data) {
int current_head = atomic_load(&cb->head);
if (current_head == atomic_load(&cb->tail)) {
return -1; // Buffer is empty
}
*data = cb->buffer[current_head];
atomic_store(&cb->head, (current_head + 1) & MASK);
return 0;
}
int main() {
CircularBuffer cb;
initializeBuffer(&cb);
// Example usage
enqueue(&cb, 'A');
enqueue(&cb, 'B');
enqueue(&cb, 'C');
char data;
while (dequeue(&cb, &data) == 0) {
printf("Dequeued: %c\n", data);
}
return 0;
}
Exercice 1 : Calcul des Écarts sur Volume et Prix Contexte :Une entreprise a prévu…
1. Généralités sur le Contrôle Budgétaire Question 1 : Quel est l’objectif principal du contrôle…
Voici un QCM Contrôle de Gestion - Pilotage de la Performance bien conçu sur le…
Une fiche d’action est un outil essentiel pour planifier, suivre et gérer les tâches dans…
La fiche de parrainage est bien plus qu’un simple document administratif. Elle constitue un outil…
La tenue de registres est une méthode essentielle pour organiser et gérer des informations de…
This website uses cookies.