[Résolu] Comment sauver une chaà®ne de caractères dans une structure C avec NSData?
Herve
Membre
Bonjour,
Je ne trouve pas la solution au problème suivant :
Une application Cocoa sauve avec NSData une structure C qu'un plug-in Audio unit (écrit en C++) récupère. Jusqu'à présent, il n'y avait que des float, int et boolean dans cette structure. Pas de problème, cela marche très bien.
J'aimerais y ajouter des chaà®nes de caractères sauvés et relus par le plug-in par la même structure C écrite sur le disque avec NSData. (le plug-in utilise quant à lui CFDataRef ).
Je n'arrive pas à utiliser CFString, ni à faire marcher des tableaux de caractères du genre
char mesNoms[32][32];
bien que ceci serait sans doute la meilleure solution en termes de compatibilité entre Cocoa et C++.
Bref, comment feriez-vous? Merci par avance pour votre aide.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Pas sûr sur le sizeof par contre... Ptêt' sizeof(char)*32*32...
Sois plus précis, c'est quoi ton problème, un problème d'encoding ?
Avec char mesNoms[32][32 tu déclares un tableau de 32 strings de 31 cars de long
C'est bien ce que tu veux ?
Montre ton code !
Mais ces 32*32 chars ne sont pas forcément dans une zone mémoire contiguë ! Tu peux avoir le premier tableau de 32 chars (la première chaà®ne de 32 caractères) à un endroit, la seconde dans une autre zone mémoire, etc. et ton tableau mesNoms[32][32] va contenir les adresses de chacune de ces zones mémoires de 32 chars.
Du coup tu ne peux pas copier tout d'un coup en une seule instruction.
Je pense qu'il faut que tu boucles sur ton tableau de 32 chaà®nes, et que tu les enregistres une par une. Un truc du genre (pas testé) :
Tu es sûr de toi Ali ? L'un de nous deux doit réviser son K&R.
Merci à tous pour ces premières réponses.
En fait, la structure à sauvegarder est faite elle-même de structures imbriquées.
La voici sans les noms :
J'aimerais ajouter dans la structure Programmes MIDI de préférence, sinon à défaut dans celle "Patch" ma chaà®ne de caractères.
<_<
char (*str[32])[32];
Si dans patch tu ajoutes char nom[32];
quel est le problème ?
"char lesNoms[32][32]" pour moi c'est un char**, donc un tableau de char* : donc tu as 32 chaà®nes, chaque chaine étant un char[32] donc un tableau de 32 chars (une chaà®ne de 31 caractères + NUL terminator)
Tu as peut-être par exemple :
- la première chaine (char[32], tableau de 31 caractères + NUL) qui commence à l'adresse 0x00cc0a00 (son premier caractère est en 0x00cc0a00, son 2ème caractère en 0x00cc0a01... son 31ème caractère en 0x00cc0a1f),
- la 2ème chaà®ne commence à l'adresse 0x00cc0b00 (ses caractères sont dans la zone mémoire qui va de 0x00cc0b00 à 0c00cc0b20, NULL compris),
- la 3e qui commence à l'adresse 0x00cd1a00,
- ...
Et ton tableau "lesNoms" est un tableau de char*, donc un tableau de pointeurs qui pointent chacun vers le premier char de chaque chaà®ne, autrement dit c'est le tableau { 0x00cc0a00, 0x00cc0b00, 0x00cd1a00, ... }.
On a donc bien un tableau de 32 pointeurs, chacun pointant vers le premier caractère d'une chaà®ne de 32 chars (NUL inclus).
Bon après y'a des chances quand même que ça le soit, vu que le compilateur va certainement allouer les zones l'une après l'autre, au final ça risque fort d'être continu. Mais conceptuellement pour mois d'après la définition de ce type, on ne devrais pas se baser sur l'espoir que les 1024 octets utilisés pour représenter ces 32 tableaux de 32 caractères soient contigus, si ?
char str[32][32] est équivalent à char (*str)[32]
c'est à dire un pointeur de 32 cars, pas 32 pointeurs de 32 cars !
alors qu'avec char (*str[32])[32];
on a bien un tableau de 32 pointeurs sur 32 cars.
Par exemple, quand tu dis "un pointeur de 32 cars" ça veut rien dire. C'est soit un "pointeur vers un tableau de 32 chars" (et si c'est ça, là oui je suis d'accord) soit "un tableau de 32 chars" (et là je suis pas d'accord)
- char* str c'est une chaà®ne (un pointeur vers un char, en vrai, mais on s'attend que ce soit le premier char d'une chaà®ne terminée par NUL)
- char str[32] c'est une chaà®ne aussi, de 31 caractères + NUL (un tableau de 32 char en vrai, donc une suite de caractères, donc une chaà®ne)
- char* str[32] j'avoue que j'hésite à dire si c'est un tableau de 32 "char*", qui sont chacun une adresse sur le début d'une chaà®ne C (c'est donc un tableau de 32 chaà®nes, ce que je dis depuis le début, mais ces 32 chaà®nes ne sont pas forcément côte à côte en mémoire), ou un pointeur vers un tableau de 32 char, donc un pointeur vers une chaà®ne (plus exactement un pointeur (char**) vers le pointeur (char*) indiquant l'adresse du premier caractère (char) de la chaà®ne). Mais en aucun cas c'est juste directement un tableau de 32 chars en tout cas comme tu as l'air de le dire mpergand (car ça c'est "char str[32]"). Ou alors tu n'as pas utilisé les bons termes
Pour moi "char str[32]" c'est un peu comme "char* str" suivi de "str = malloc(32*sizeof(char))" suivi d'un "free" automatique à la sortie du contexte. C'est un pointeur vers le premier char d'une zone mémoire contenant 32 chars les uns derrière les autres. Juste "char* str" c'est un pointeur vers un char, mais "char str[32]" implique que derrière on alloue sur le tas la place pour 32 de ces chars qui vont être les uns derrière les autres en mémoire, et notre pointeur str pointe vers le premier char de cette suite de 32 chars, str étant ainsi un tableau de 32 chars, autrement dit une chaà®ne de 31 caractères + NUL.
Cela fait toujours du bien de discuter de son problème avec les autres. je ne sais pas pourquoi, mais suite à mon post, j'ai eu l'idée de faire :
Et là , plus de problème, tout marche très bien. C'est marrant comment on ne voit pas les trucs simples qui tombent sous le sens!!!
Je ne comprends pas en quoi mettre le tableau de cars dans une structure résoud ton problème (d'ailleurs j'ai toujours pas compris lequel)
Donc tu as 32 noms de patchs qui font chacun 32 cars.
tu y accèdes par
char* nomPatch=lesNomsDesPatchs[n].lesLettres; // lesLettres est l'adresse du tableau de 32 cars que l'on affecte à un pointeur
si tu n'utilises pas de struct, tu déclares:
char lesNomsDesPatchs[32][32];
et tu y accèdes par:
char* nomPatch=lesNomsDesPatchs[n];
Pourquoi faire compliqué ...
Pour moi char toto [32][32] réserve une zone de 1024 chars, char * toto [32] réserve une zone de 32 pointeurs sur des chars. Et un pointeur n'a pas la même taille qu'un char.
Non, on n'est pas d'accord
Car un tableau ne contient pas de pointeurs quelque soit sa dimension.
Si tel était le cas, alors je pourrais écrire:
char str[2][32];
str[1]++;
et là compilo pas content !
Un tableau est seulement une adresse en mémoire, pas un pointeur.
Par contre en paramètre d'une fonction je peux faire:
void ff(char str[]) // ou char* str
{
str++;
}
ici char str[] est équivalent à char* str, car le paramètre est un pointeur créé dans la pile
Car par exemple quand tu dis : Je ne comprends pas trop... "char* tab[32]" n'est pas un tableau de 32 "char*", et donc un tableau de pointeurs vers des char ? "int* tab[32]" n'est pas un tableau qui contient des pointeurs vers des int ?
Car par exemple quand tu dis : Oui, là par contre c'est moi qui ai pris un raccourci et fait l'amalgame entre pointeur et adresse mémoire, c'est vrai. Au lieu de dire "un tableau de pointeurs vers des char" j'aurais dû dire "un tableau d'adresses mémoires", ou plus exactement "une adresse mémoire correspondant au début d'une zone mémoire de taille suffisante pour stocker à la suite 32 adresses mémoires, chacune de ces 32 adresses mémoire étant le début d'une zone mémoire pour stocker les 31 caractères + NUL de la chaà®ne".
En plus, avec une déclaration "[]" la zone mémoire est réservée à la compilation, dans le tas, et donc son adresse est fixée à la compilation et ne peut pas être changée au runtime. D'où le fait qu'on ne puisse pas faire un "++" dessus (je me demande si on ne peut pas considérer que "char str[2][32]" n'est pas plutôt équivalent à "char *const *const" en terme de typage, dans le sens où ce sont des zones mémoires allouées sur le tas et dont on ne peut pas changer les adresses ensuite, mais bon je ne sais pas si c'est vraiment une bonne façon de voir les choses)
C'est là qu'il faut que tu révises ton K&R. char toto [32][32] ça réserve une zone mémoire de 1024 chars. Y'a pas de pointeurs ni d'adresses mémoire là -dedans.
Donc dans ce cas, toto est un char* ou un char** ? Si je dois écrire une fonction et lui passer toto en paramètre, je dois écrire "void f(char*)" et pas "void f(char**)" du coup ? Et quel est le type de "toto[5]" ?
Et si "char toto [32][32]" est exactement la même chose que char "toto[1024]" au niveau réservation de la zone mémoire :
- Est-ce que le compilo va gueuler si j'écris "toto[40][2]" dépassant les bornes du 32*32 ou est-ce qu'il s'en fout puisque de toutes façons il traduit ça par 40*32+2 et que ça reste <1024 ?
- Pourquoi le code d'origine de Hervé, consistant à créer un NSData directement à partir de l'adresse de cette zone contiguë de 1024 chars, ne marchait pas ?
Tu veux que je réponde à tes questions ou elles sont seulement rhétoriques ?
Tu as peut-être eu le temps de tester quelques lignes de C ?
Petit cours sur les tableaux du C for newbie
Beuh, le C ça vérifie rien, c'est "No string attached"
si je déclare un tableau à deux dimensions:
int tab[2][10];
quand je fais :
int* tab1=tab[1];
tab[1] est une adresse qui est le résultat d'un calcul par le compilo (adresse de tab+10)
si je veux passer ce tableau à une fonction, il faut que je spécifie la taille du premier tableau.
Pour s'en sortir il faut regarder quel est le type dont on va créer un tableau et savoir que toutes les cases d'un tableau sont forcément contiguës (quelque soit le nombre de dimension).
char tab1[32][32]; => sizeof(tab1) = 1024
char *tab2[32]; => sizeof(tab2) = 256 = 32*8 (sur un mac 64 bits)
char *tab3[32][32];=> sizeof(tab3) = 8192 = 32*32*8 (sur un mac 64 bits)
La version verbose :
1/ char tab[32][32] crée un tableau d'éléments de type char. Et il y en a 32*32.
Cela donne bien une zone mémoire de 1024 char consécutif. La variable "tab" va contenir l'adresse de cette zone mémoire.
tab:[ adresse (disons 64 bits) ]---> [ ] [ ] [ ] ... (32 cases de 1 octet)
[ ] [ ] [ ]
... (32 lignes)
En mémoire, les lignes sont consécutives pour faciliter le travail du compilateur :
tab:[ ]---> [ ] [ ] ... 1024 cases.
2/ char *tab[32] = {"chaine1", "chaineDeux", ...}
Ici le type est char *, donc on crée un tableau de 32 char *.
Donc on obtient une zone mémoire de taille = 32*(taille d'un pointeur) octets pour le tableau.
Dans mon exemple les valeurs de chaque case du tableau seront initialisées avec l'adresse des chaines constantes ("chaine 1", etc).
Les zones mémoires qui stockent ces chaines constantes ne sont pas forcément contiguës en mémoire.
tab:[ adresse (64bits) ]---> [ adresse 64 bits ] [ adresse 64 bits ] ... (32 cases de 8 octets)
| |
| |----> [c] [h] [a] [n] [e] [d] ... (10 octets)
|
|---> [c] [h] [a] [n] [e] [1] (7 octets)
ou plus simplement:
Ce qui donne:
ad tab 0xbfffe980 ad tab[0] 0xbfffe980 ad tab[0][0] 0xbfffe980
Tout ça est à la même adresse.
En gros, j'a confondu qui alloue une zone mémoire pour 32*32=1024 chars contigus avec qui alloue une zone mémoire pour 32 char* contigus, chaque char* pointant sur une zone mémoire allouée pour de 32 chars.
Donc au final la première réponse de Larme était correcte et ça devrait marcher.
Le probleme ici pouvait etre un probleme de padding dans les structures. On n'a pas vraiment assez de code pour savoir.
En tous cas, quand on cherche a serialiser des structures en C, il faut etre conscient de ce probleme :
Les compilateurs ajoutent des octets de padding entre les membres des structures pour aligner chaque membre de la structure sur des adresses multiples de 2,4 ou 8 octets en fonction de leurs optimisations.
Si on veut éviter cela, il faut utiliser des options de compilations ou des pragma pour ordonner au complateur de ne pas ajouter d'octets de padding. (#pragma pack ou ___attribute(packed)__).
En clair si vous une structure avec un uint8 suivi d'un uint16, sizeof peut renvoyer 4 au lieu de 3 comme on pourrait s'y attendre.
NSData *data = [NSData dataWithBytes:&mesNoms length:sizeof(mesNoms)]
Ici le compilateur est gentil car l'adresse d'un tableau ça n'existe pas.
[ EDIT je viens de vérifier ça marche pas
en fait c'est bizarre si je fais:
printf("%p %p \n",tab[1],&tab[1]);
ça affiche la même adresse
mais char* str=&tab[1];
produit un warning
]
Pourtant en Objective-C je fais souvent des
par exemple, ou encore j'utilise parfois l'initialisation de structures ou de tableaux via champs nommés, comme
Qui sont pourtant des astuces purement C que tout le monde ne connais pas... mais les tableaux de tableaux, c'est déjà moins ma tasse de thé je l'avoue Donc merci ça m'a pas fait de mal ces petites révisions
Beuh! c'est quoi ça du Pascal
C'est valable avec quelle version du compilateur ?
Designated Initializers " GCC GNU documentation
Je l'utilise rarement pour les tableaux, mais très souvent pour les structures, surtout les constantes.
Par exemple comme je suis très actif contre ceux qui mettent des Magic Numbers partout dans leur code et que j'impose à tout le monde d'utiliser des constantes plutôt que des valeurs en dur, faut bien parfois déclarer des CGSize ou des CGRect en constantes. Mais CGSizeMake ou CGRectMake ne marchent pas dans ce contexte car sont des fonctions C non-const.
Donc pour déclarer une CGSize constante, rien de plus simple :
Certes, on pourrait juste écrire ceci, ça marche aussi parfaitement
Mais je trouve la version détaillée plus claire pour éviter de se mélanger les pinceaux.
Et encore, sur CGSize ça va je peux me passer de préciser les noms des champs, la 2e version me choque pas. Mais sur UIEdgeInsets où je sais jamais si c'est left/top/right/bottom ou top/left/bottom/right ou autre, je précise toujours
---
C'est également très utile pour définir une CGRect via son origin et sa size plutôt que de devoir découper ça avec les 4 composantes x,y,width,height :
Ce qui est quand même plus simple que de devoir préciser les 4 paramètres
alors qu'ils sont issus chacun d'une structure déjà toute prête newOrigin ou kLabelSize !
On peut également écrire des choses comme ça, façon KeyPath, mais bon là je vais rarement voire jamais jusque-là :
Super je regarde çà !