Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Empêcher l'accès aux données d'un struct

36 réponses
Avatar
TSalm
Bonjour,

Voici une petite question de noob motivé.
Venant du monde de la POO, j'ai démarrer un pgm en C avec un struct qui
contient les données, et des fonctions qui s'appliquent à ce struct.
Par exemple, dans le header, j'ai ceci :
/* ---------- HEADER ---------- */
typedef struct
(int data1
,int data2
) MonStruct;

void MonStruct_create();
void MonStruct_free(MonStruct*);
void MonStruct_fait_quelquechose(MonStruct*,int data);
/* -------------------------- */

Mon problème est que les données("data1" et "data2") de "MonStruct"
pourraient être, par inadvertance (ou plutôt feignantise ;-) ),
directement modifiées sans passer par les fonctions.
Est-il possible de pouvoir déclarer les données "data1" et "data2" dans
l'implémentation (le fichier ".c"), de façon à ce que l'utilisation de
MonStruct ne puisse se faire qu'en passant par les fonctions ?

D'avance merci pour vos avis ou éventuelles remarques.
-TSalm

6 réponses

1 2 3 4
Avatar
Pascal J. Bourguignon
Samuel DEVULDER writes:

1950? Oui donc c'est vraiment de l'archéo-programmation ce problème de
modèle mémoire distinct entre char* et struct*. Sur les machines
d'après 1980 cette problématique ne devrait plus être très fréquente.



Détrompes toi. Il y a beaucoup de processeurs qui ne sont pas des
machines 32-bit ou 64-bit avec capables d'adresser des octets. Par
exemple les DSP. Ils ont souvent des mots de 24 bits ou de 21 bits et
n'adressent que des mots.

--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
Avatar
Jean-Marc Bourguet
Samuel DEVULDER writes:

Le 04/02/2012 22:35, Jean-Marc Bourguet a écrit :

struct s { char c; }

Soit tu as sizeof(struct s) == 1, et tu utilises le même format que char*
pour toutes les structs, soit tu ajoutes du padding



Donc sur ces machines, suivant qu'on pointe sur un simple caractère ou
sur quelque chose de plus structuré, on a pas les mêmes pointeur si je
comprends bien ce que tu me dis. Pourquoi ne pas avoir choisi un
architecture homogène avec des pointeurs tous identiques ?



Je ne suis pas sûr de comprendre la question. Tu implémentes le language
pour les archi dont tu disposes (même s'il y a une boucle de rétro-action,
l'usage que les langages font d'une archi influence les implémentations
futures et son évolution).

Historiquement, on avait deux classes de machines. Les machines
commerciales, adressables par caractère -- de 6 bits -- ou par chiffre
décimal, à l'arithmétique décimale avec des données de tailles variables
(pour les machines à caractères, on avait un chiffre par caractère et
ignorait les deux bits en plus pour l'arithmétique, ou bien ils marquaient
le signe et la fin des nombres). Et les machines scientifiques, adressables
par mot, avec des mots longs (genre 36 bits) et une taille d'adresse bien
plus petite (genre 12 ou 18 bits).

Au milieu des années 60, l'IBM 360 a voulu fusionner les deux et introduit
les machines comme on les connait (adressable par bytes de 8 bits et
utilisant une arithmétique binaire avec des données de tailles fixes).
Mais les anciens types d'architecture n'ont pas disparu pour autant, en
particulier les machines adressables par mots sont restées populaires
jusque dans les années 70 (au début, les PDP-10 étaient l'archi la plus
représentées sur Arpanet) et d'une importance certaine bien que déclinante
dans les années 80 et même 90 (DEC a annoncé l'arrêt des PDP-10 vers 84, il
y a eu de fabriquants de machines compatibles jusque dans les années 2000).
Les Cray 1 (1976) avaient un mot de 64 bits et des adresses sur 24, les
adresses ont été étendues jusqu'à 32 bits pour les Cray Y-MP (1992). Et
même de nos jours, Unisys continue à vendre des machines adressables par
mot avec des mots de 36 et 48 bits dont les ancêtres remonte aux années 50
et 60 (je ne sais pas comment ils ont résolu le problème que devait leur
poser le manque de bit d'adresses des modèles initiaux), mais j'avoue que
pour moi leur intérêt n'est plus qu'annecdotique et je doute qu'elles
soient souvent programmées en C, bien qu'Unisys propose un compilateur C
pour au moins une d'entre elle.

C'est plutôt l'inverse, les char* (et les void*) plus grand que les struct*.



struct s { char c; };
struct t { struct s; } toto;

struct t *pt = &toto;
struct s *ps = &pt->s;
char *pc = &ps->c;

Donc:
sizeof(pt) == sizeof(ps) car ce sont deux pointeurs sur struct, mais
sizeof(ps) != sizeof(pc).

Comment fait le compilo pour générer un appel à memset(ps, 0, 1) et à
memset(pc, 0, 1)?

Y a-t-il deux versions de memset, une pour les "char*" et l'autre pour
les "struct*" ? Non ca ne semble complexe il faudrait faire ca pour
toutes les fonctions acceptant un void*. Je suppose qu'en réalité
le compilo va caster le struct *ps en void* et empiler un pointeur
plus grand qu'à l'origine.



C'est ce que je ferais. Et ce genre de chose montre que le cast en void* ou
en char* doit modifier la représentation si besoin est, contrairement à ce
que je disais.

1950? Oui donc c'est vraiment de l'archéo-programmation ce problème de
modèle mémoire distinct entre char* et struct*. Sur les machines
d'après 1980 cette problématique ne devrait plus être très fréquente.



En 1980, la disparition des machines adressables par mot pour les
utilisations non spécialisées était prévisible mais pas certaine. En 1990
c'était plié, mais pas terminé.

Et les processeurs adressables par mot existent toujours pour des
applications spécialisées (comme des DSP). Mais les implémentations de C
font plutôt le choix char=int que d'avoir des pointeurs de tailles
différentes, parce que c'est plus simple pour des machines n'ayant
normalement pas à manipuler des caractères.

Les machines historiques elles devaient manipuler des caractères et n'en
mettre qu'un par mot ou doubler la taille de tout les pointeurs aurait été
inconcevable à l'époque considérant la taille des mémoires.

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
cLx
On 04/02/2012 21:45, Samuel DEVULDER wrote:
Finalement, à chaque niveau d'abstraction correspond son langage type.
Si on est proche de l'architecture matérielle, on a l'assembleur où il
faut tout gérer; si on veut s'abstraire du matériel via un OS, alors
on fait du C (ansi?). On gère moins de chose bas niveau et les pointeurs
sont tous compatibles les uns avec les autres à cause des bibliothèques
standards mais il faut encore s'occuper de l'allocation/libération de
mémoire par exemple; enfin si on veut s'abstraire encore plus de l'OS
il y a les langages où ce dernier est complètement masqué, où la notion
de pointeur a complètement disparue et où l'on ne se préoccupe plus delibérer
les resources à la main par exemple.




En même temps, pour les microcontrôleurs, il y a des compillos C qui n'ont
pas grand chose d'ansi* et qui permettent déjà de pas mal abstraire les
choses (on peut changer de µP sans modifier le code, enfin, et on a les mêmes
IO ..)

* sur les pics, les données et le programme ne sont pas dans la même mémoire,
et les bus n'ont même pas la même largeur. Ça rend parfois impossible de
faire faire des trucs pourtant courants sur des PC, mais il y a quand même
des avantages de bosser en C par rapport en ASM.
Avatar
Antoine Leca
Samuel DEVULDER écrivit :
En tout cas il semble que toutes les "struct*" ont la même taille



C'est une contrainte imposée par la norme, donc de fait c'est difficile
de trouver des cas où ce n'est pas respecté puisque ce serait non
conforme, donc rendrait difficile la portabilité, ce serait absurde.

(c'etait le point de départ de la discussion).



Le point de départ c'était de comprendre si cette contrainte d'unité des
tailles at alignements des pointeurs vers structures était (seulement)
justifiée par la nécessité de pouvoir traiter les structures incomplètes.

Là où est arrivée la discussion, j'en retire que cette contrainte est de
toute manière largement superflue (aujourd'hui).
Un peu comme les bytes devant faire au moins 8 bits.


struct s { char c; } *ps;
struct t { struct s; } *pt;
char *pc = &ps->c;

Donc:
sizeof(pt) == sizeof(ps) car ce sont deux pointeurs sur struct, mais
sizeof(ps) != sizeof(pc).

Comment fait le compilo pour générer un appel à memset(ps, 0, 1) et à
memset(pc, 0, 1)?

Y a-t-il deux versions de memset, une pour les "char*" et l'autre pour
les "struct*" ?



Pour un compilateur optimiseur qui déroule les boucles, c'est possible.
Sinon, la fonction memset() de la bibliothèque ne sait traiter que les
char* (identiques en représentation aux void*).

Je suppose qu'en réalité le compilo va caster le struct *ps en void*
et empiler un pointeur plus grand qu'à l'origine.



Si tu remplaces memset() par une fonction anonyme, c'est même ce que la
norme oblige à faire ; et ce transtypage implicite est une des grandes
raisons qui ont justifié l'introduction des prototypes dans la norme
(qui vaut aussi pour les autres types avec plusieurs représentations,
comme les entiers ou les flottants).


Antoine
Avatar
Jean-Marc Bourguet
Antoine Leca writes:

Là où est arrivée la discussion, j'en retire que cette con trainte est de
toute manière largement superflue (aujourd'hui).



C'est plutot la possibilite d'avoir sizeof(X*)!=sizeof(Y*) qui est
supperflue.

Un peu comme les bytes devant faire au moins 8 bits.



Si tu te liberes de la possibilite et que tu forces a avoir CHAR_BIT==8,
la il y a des implementations pour DSP qui vont avoir des problemes. Tu
peux forcer CHAR_BIT==8 (et encore, je ne suis pas sur que tous ont des
char de tailles multiples de 8), mais alors les implementations auront
tentance a vouloir des pointeurs de tailles differentes (bon, ils
peuvent ajouter du padding, mais avec un aussi mauvais support de la
part du langage, il y a des chances qu'ils choississent d'etre non
conforme, et vu leur dommaince d'application, c'est CHAR_BIT qui va
grandir)

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Antoine Leca
Jean-Marc Bourguet écrivit :
Antoine Leca writes:

Là où est arrivée la discussion, j'en retire que cette contrainte est de
toute manière largement superflue (aujourd'hui).



C'est plutot la possibilite d'avoir sizeof(X*)!=sizeof(Y*) qui est
supperflue.



Ce qui amène au point précédent, nous sommes d'accord.

Un peu comme les bytes devant faire au moins 8 bits.



Si tu te liberes de la possibilite et que tu forces a avoir CHAR_BIT==8,



[couic] ...mais ce n'était pas mon propos : j'étais resté sur les
architectures, aujourd'hui obsolètes mais déjà vieillissantes dans les
années 1980 (cf. ton message) avec des "bytes" de 6 ou 7 bits.


Antoine
1 2 3 4