Un peu de généricité en C

a marqué ce sujet comme résolu.
Auteur du sujet

Salut,

fort de ma lecture rapide du tutoriel C pour me remettre dans le bain de ce merveilleux langage, j’ai décidé de me pencher sur la quesion de la généricité (notamment avec les pointeurs génériques) pour voir ce que ça vaut, en implémentant des tableaux génériques redimensionnables.

Voilà ce que ça donne :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define GET_INT(t, i) *((int *)t.get(&t, i))

typedef struct vector Vector;

struct vector {
    size_t data_size;
    void *data;
    size_t size;
    size_t capacity;
    
    void* (*get)(Vector*, int);
    void (*push)(Vector*, void*);
    void (*set)(Vector*, int, void*);
};

void* vector_get(Vector* this, int index) {
    // arithmetic operator is invalid on void pointer
    return (void *)((size_t)this->data + this->data_size * index);
}

void vector_set(Vector* this, int index, void* val) {
    void *to = (void *)((size_t)this->data + this->data_size * index);
    memcpy(to, val, this->data_size);
}

void vector_push(Vector* this, void* val) {
    if (this->size == this->capacity) {
        this->capacity = this->capacity * 2 + 1;
        this->data = realloc((char *)this->data, this->capacity * this->data_size);
    }
    this->size++;
    vector_set(this, this->size-1, val);
}

Vector vector_new(size_t data_size) {
    Vector tmp = {
        .data_size = data_size,
        .data = NULL,
        .size = 0,
        .capacity = 0,
        .get = vector_get,
        .set = vector_set,
        .push = vector_push
    };
    return tmp;
}

void vector_free(Vector *t) {
    free(t->data);
    t->size = 0;
    t->capacity = 0;
}

int main() {
    Vector t = vector_new(sizeof(int));

    for (int i=0; i<10; ++i) t.push(&t, &i);
    for (int i=0; i<t.size; ++i) printf("%d ", GET_INT(t, i));
    printf("\n");

    for (int i=0; i<t.size; ++i) {
        int sqr = i * i;
        t.set(&t, i, &sqr);
    }

    for (int i=0; i<t.size; ++i) printf("%d ", GET_INT(t, i));
    printf("\n");

    vector_free(&t);

    return 0;
}

Si quelqu’un a déjà fait ce genre de choses en C, ça m’intéresserait de savoir dans quel contexte :). Plus généralement n’importe quelle critique ou remarque par rapport au code est la bienvenue !

EDIT: à la lecture de code je vois souvent les pointeurs écrits type *ptr mais malgré tout je trouve que l’écrire type* ptr (qui indique mieux que la variable ptr a le type type* à mon gout). Des avis ?

Édité par Skodt

+0 -0

Lu’!

Ça manque de traitement des erreurs dans realloc ;)

EDIT: à la lecture de code je vois souvent les pointeurs écrits type *ptr mais malgré tout je trouve que l’écrire type* ptr (qui indique mieux que la variable ptr a le type type* à mon gout). Des avis ?

Skodt

C’est parce que dans la norme C, l’étoile est, d’après la syntaxe, rattachée au nom de la variable. Ce qui fait que:

int * p, q ;

déclare bien un pointeur et un entier.

Si quelqu’un a déjà fait ce genre de choses en C, ça m’intéresserait de savoir dans quel contexte :).

Oh oui. Dans le contexte d’un OS. Un truc complètement contraire à la norme (plus précisément contre le strict aliasing), parce que sinon pas moyen d’avoir toutes les bonnes propriétés sur la SDD.

Édité par Ksass`Peuk

First : Always RTFM - "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein [Tutoriel Frama-C WP]

+0 -0
Auteur du sujet

Ça manque de traitement des erreurs dans realloc ;)

Justement à ce propos, vaut-il mieux renvoyer un code d’erreur et laisser l’utilisateur gérer ça derrière (quand un realloc fail, l’ancien pointeur reste valide après tout), ou bien terminer l’exécution ?

Je pense qu’il serait plus propre de caster les this->data en (char*) plutôt qu’en (size_t) puisqu’il s’agit de pointeurs.

C’est noté, en effet ça fait plus propre :)

+0 -0

Justement à ce propos, vaut-il mieux renvoyer un code d’erreur et laisser l’utilisateur gérer ça derrière (quand un realloc fail, l’ancien pointeur reste valide après tout), ou bien terminer l’exécution ?

Peu importe, mais il faut pouvoir le libéré. Que ce soit toi maintenant ou plus tard quand l’utilisateur te le demandera.

void* (*get)(Vector*, int);
void (*push)(Vector*, void*);
void (*set)(Vector*, int, void*);

Pas sûr de comprendre bien pourquoi tu veux faire l’OO. C’est pas vraiment dans la philosophie du langage.

this->size++; vector_set(this, this->size-1, val);

?

vector_set(this, this->size++, val);

Sinon :

Vector vector_new(size_t data_size) {
    return (Vector)({
        .data_size = data_size,
        .data = NULL,
        .size = 0,
        .capacity = 0,
        .get = vector_get,
        .set = vector_set,
        .push = vector_push
    });
}

En cherchant bien, je pense que j’ai trouvé un petit bug.

void vector_free(Vector *t) {
    free(t->data);
    t->size = 0;
    t->capacity = 0;
}

Il manque :

t->data = NULL

Qui risque d’introduire des problèmes avec realloc si tu push ensuite.

Édité par ache

ache.one                 🦹         👾                                🦊

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte