Forum Solarus-Games francophone

Jeux amateurs => Programmation => Discussion démarrée par: Wouf le 03 Avril 2011 à 20:30

Titre: Types de taille fixe en C
Posté par: Wouf le 03 Avril 2011 à 20:30
Besoin de vos lumières, oh suprêmes programmeurs qui hantent ces couloirs :)

J'étais en train de parler prog avec Yoshi, quand soudain, on s'est demandé ce que voulait dire le type long long en C.

Je me suis souvenu qu'en C, un entier avait une taille dépendante de l'architecture du processeur.
Ainsi, un int est codé sur 16 bits pour un vieux processeur 16 bits, sur 32 bits pour un processeur commun de 32 bits et sur 64 bits pour les nouveaux processeurs 64 bits.
Donc voilà, dans la plupart des cas, un int serait codé sur 32 bits, puisque c'est encore l'architecture la plus courante.

Idem pour les unsigned qui sont juste strictement positifs et ont leur propre algèbre, bref ils ne changent en rien le nombre de bits de l'encodage.

J'ai aussi entendu dire qu'à l'époque, un entier de type long était plus long qu'un entier de type int, mais que ce n'est plus le cas de nos jour. Mm ok, mais depuis quelle norme ? C89 ou C99 ?

Il y a quelques mois, j'avais zyeuté le code source de la Glib et j'avais appris qu'il y avait moyen de définir des types de taille fixe, peu importe l'architecture du processeur, en jouant avec des préfixes de int (short, long, long long, ...). D'après le code source (http://git.gnome.org/browse/glib/tree/glib/glibconfig.h.win32.in) :

Citation de: glib/glibconfig.h.win32.in
typedef signed char gint8;
typedef unsigned char guint8;
typedef signed short gint16;
typedef unsigned short guint16;

typedef signed int gint32;
typedef unsigned int guint32;

#ifndef _MSC_VER
G_GNUC_EXTENSION typedef signed long long gint64;
G_GNUC_EXTENSION typedef unsigned long long guint64;
#else /* _MSC_VER */
typedef signed __int64 gint64;
typedef unsigned __int64 guint64;
#endif /* _MSC_VER */

et la suite, dans gtypes.h :

Citation de: glib/gtypes.h
/* Provide type definitions for commonly used types.
*  These are useful because a "gint8" can be adjusted
*  to be 1 byte (8 bits) on all platforms. Similarly and
*  more importantly, "gint32" can be adjusted to be
*  4 bytes (32 bits) on all platforms.
*/

typedef char   gchar;
typedef short  gshort;
typedef long   glong;
typedef int    gint;
typedef gint   gboolean;

typedef unsigned char   guchar;
typedef unsigned short  gushort;
typedef unsigned long   gulong;
typedef unsigned int    guint;

Donc, j'en conclu :

- (unsigned) int donne un entier de taille variable (dépend architecture)
- (un)signed short donne un entier sur 16 bits
- (un)signed int donne un entier sur 32 bits (ah déjà une incohérence, j'en reparle plus loin)
- (un)signed long long donne un entier sur 64 bits, sous Windows
- (un)signed __int64 donne pareil, sous les autres OS

Si je comprends bien, signed force le nombre de bits, puisque signed int est codé sur 32 et int sur un nombre variable de bits.
Mais alors, pourquoi unisigned int est utilisé à la fois pour un entier non signé sur 32 bits et aussi pour un entier non signé sur nombre variable de bits :o ???

Second bémol, il me semblait qu'on devait écrire short int et non short tout court (pas fait exprès :mrgreen:) !? Serait-ce comme pour long, qu'on utilise. Pourtant j'ai déjà entendu parler de long int. Quelle est la différence ? Depuis quelle norme ?

Là, un doute me prend, je vais zyeuter le code source de la SDL qui, d'après mes souvenirs, définissait le même genre d'entiers à taille fixe. J'y trouve dans ce fichier :

Citation de: include/SDL_stdinc.h
67 #if defined(HAVE_INTTYPES_H)
68 # include <inttypes.h>
69 #elif defined(HAVE_STDINT_H)
70 # include <stdint.h>
71 #endif

...

122 /**
123  * \brief A signed 8-bit integer type.
124  */
125 typedef int8_t Sint8;
126 /**
127  * \brief An unsigned 8-bit integer type.
128  */
129 typedef uint8_t Uint8;
130 /**
131  * \brief A signed 16-bit integer type.
132  */
133 typedef int16_t Sint16;
134 /**
135  * \brief An unsigned 16-bit integer type.
136  */
137 typedef uint16_t Uint16;
138 /**
139  * \brief A signed 32-bit integer type.
140  */
141 typedef int32_t Sint32;
142 /**
143  * \brief An unsigned 32-bit integer type.
144  */
145 typedef uint32_t Uint32;
146
147 /**
148  * \brief A signed 64-bit integer type.
149  */
150 typedef int64_t Sint64;
151 /**
152  * \brief An unsigned 64-bit integer type.
153  */
154 typedef uint64_t Uint64;
155
156 /*@}*//*Basic data types*/

J'en conclu que les types (u)intXX_t sont définis dans l'un de entêtes inttypes.h ou stdint.h de la lib standard.
J'apprends par ailleurs qu'ils n'ont été introduits qu'à la norme C99, ce qui ne me plait pas beaucoup, m'enfin, passons (bien qu'étonnant pour une lib qui se veut très rétro-compatible).
Regardons ensemble un extrait de la spécification de l'entête stdint.h :

Citation de: specification de stdint.h
Integer Types

When typedef names differing only in the absence or presence of the initial u are defined, they shall denote corresponding signed and unsigned types as described in the ISO/IEC 9899:1999 standard, Section 6.2.5; an implementation providing one of these corresponding types shall also provide the other.

In the following descriptions, the symbol N represents an unsigned decimal integer with no leading zeros (for example, 8 or 24, but not 04 or 048).

   *

     Exact-width integer types

     The typedef name int N _t designates a signed integer type with width N, no padding bits, and a two's-complement representation. Thus, int8_t denotes a signed integer type with a width of exactly 8 bits.

     The typedef name uint N _t designates an unsigned integer type with width N. Thus, uint24_t denotes an unsigned integer type with a width of exactly 24 bits.

     [CX] [Option Start] The following types are required:


     int8_t
     int16_t
     int32_t
     uint8_t
     uint16_t
     uint32_t
     [Option End]

     If an implementation provides integer types with width 64 that meet these requirements, then the following types are required: int64_t uint64_t

     [CX] [Option Start] In particular, this will be the case if any of the following are true:
         o

           The implementation supports the _POSIX_V7_ILP32_OFFBIG programming environment and the application is being built in the _POSIX_V7_ILP32_OFFBIG programming environment (see the Shell and Utilities volume of POSIX.1-2008, c99, Programming Environments).
         o

           The implementation supports the _POSIX_V7_LP64_OFF64 programming environment and the application is being built in the _POSIX_V7_LP64_OFF64 programming environment.
         o

           The implementation supports the _POSIX_V7_LPBIG_OFFBIG programming environment and the application is being built in the _POSIX_V7_LPBIG_OFFBIG programming environment.
     [Option End]

     All other types of this form are optional.

On est donc bien clairs sur ce points : (u)int32_t doit être codé sur exactement 32 bits, peu importe l'architecture !!
Voyons en, ensemble, l'implémentation gcc linux  :

Citation de: stdint.h de gcc (linux)
#ifndef __int8_t_defined
# define __int8_t_defined
typedef signed char      int8_t;
typedef short int      int16_t;
typedef int         int32_t;
# if __WORDSIZE == 64
typedef long int      int64_t;
# else
__extension__
typedef long long int      int64_t;
# endif
#endif

/* Unsigned.  */
typedef unsigned char      uint8_t;
typedef unsigned short int   uint16_t;
#ifndef __uint32_t_defined
typedef unsigned int      uint32_t;
# define __uint32_t_defined
#endif
#if __WORDSIZE == 64
typedef unsigned long int   uint64_t;
#else
__extension__
typedef unsigned long long int   uint64_t;
#endif

WTF, c'est différent x3
Et là aussi, on définit (u)int32_t comme un simple int. Or ce dernier est à taille variable et la spécification ne veut pas que ce soit le cas, alors que croire à la fin ? :blink:
De plus, les signed ne sont pas au rendez-vous ... Donc ils ne forcent pas la taille des types. Ce sont justes des mots clés inutiles, car par défaut le type est toujours signé, c'est ça ??
Puis bon, pourquoi on n'y définit que __int8_t_defined et __uint32_t_defined :ninja: ?
Bon, passons. Je suppose que le fichier est encore différent sous stdint.h de windows ...

Donc bon, un peu la galère pour s'y retrouver ^^
En fait, l'implémentation la plus claire que j'aie pu trouver de stdint.h est celle-ci.

Sinon, j'ai trouvé d'autres implémentations, comme celle-ci. Tout est encore différent ...
Heu ... Je nage moi, c'est quoi cette mélasse ? Qui a raison ? Les autres implémentation sont-elles foireuses ?  :o


Alors voilà, ce que j'aimerais bien savoir à propos des types ci-dessous, c'est :
- le nombre de bits sur lequel ils sont codés
- par rapport à quelle norme (C89 ou C99)
- sur quels OS

liste :

- int
- signed int
- unsigned int

- short
- signed short
- unisigned short

- short int
- signed short int
- unisigned short int

- long
- signed long
- unsigned long

- long int
- signed long int
- unsigned long int

- long long
- signed long long
- unsigned long long


Je vous remercie pour votre attention et espère que quelqu'un pourra m'indiquer la sortie du tunnel  ^_^
Titre: Re : Types de taille fixe en C
Posté par: Noxneo le 05 Avril 2011 à 18:16
Je dois bientôt partir et j'ai pas trop le temps de m'y plonger là, mais pour tenter une première réponse brève:

En C89, la spec de K&R est assez brève et nous dit:




2.2 Data Types and Sizes

There are only a few basic data types in C:

char a single byte, capable of holding one character in the local character set
int an integer, typically reflecting the natural size of integers on the host machine
float single-precision floating point
double double-precision floating point


In addition, there are a number of qualifiers that can be applied to these basic types. short and long apply to integers:

   short int sh;
   long int counter;

The word int can be omitted in such declarations, and typically it is.

The intent is that short and long should provide different lengths of integers where practical; int will normally be the natural size for a particular machine. short is often 16 bits long, and int either 16 or 32 bits. Each compiler is free to choose appropriate sizes for its own
hardware, subject only to the the restriction that shorts and ints are at least 16 bits, longs are at least 32 bits, and short is no longer than int, which is no longer than long.

The qualifier signed or unsigned may be applied to char or any integer. unsigned numbers are always positive or zero, and obey the laws of arithmetic modulo 2 n, where n is the number of bits in the type. So, for instance, if chars are 8 bits, unsigned char variables have values between 0 and 255, while signed chars have values between -128 and 127 (in a two's complement machine.) Whether plain chars are signed or unsigned is machine-dependent, but printable characters are always positive. The type long double specifies extended-precision floating point. As with integers, the sizes of floating-point objects are implementation-defined; float, double and long double could represent one, two or three distinct sizes. The standard headers <limits.h> and <float.h> contain symbolic constants for all of these sizes, along with other properties of the machine and compiler. These are discussed in Appendix B.




En C-99 c'est une autre histoire; le document qui t'intéresse est la spec ISO-IEC-9899-1999 (http://www.nirvani.net.nyud.net:8090/docs/ansi_c.pdf), qui comporte plus de détails- la page 99 (chapitre 6.7.2) devrait répondre partiellement à tes interrogations (et où on apprend qu'il y'a aussi des types optionnels _Imaginary et _Complex :D ).


Sinon, il faut garder à l'esprit que les compilateurs sont parfois un peu fantaisistes, et que du coup ce qui vaut pour GCC ne vaudra pas forcément ailleurs.


CitationAlors voilà, ce que j'aimerais bien savoir à propos des types ci-dessous, c'est :
- le nombre de bits sur lequel ils sont codés
- par rapport à quelle norme (C89 ou C99)
- sur quels OS

Ça me donnerait bien une petite idée de TP ça...c'est pas dur à tester expérimentalement ce genre de choses :D
Titre: Re : Types de taille fixe en C
Posté par: Wouf le 05 Avril 2011 à 23:58
Ah chouette, quelqu'un a répondu :ninja:

Ouais, je vois, rien de bien défini pour le C89 apparemment ... D'un autre côté, le C étant utilisé comme langage système, ça n'a pas énormément d'importance (cible spécifique). Dans le cadre d'applications portables, en revanche, c'est pas très cool, surtout pour des problèmes numériques :/
Quant aux économies de mémoire, c'est vrai qu'elle deviennent de plus en plus négligeables ; autant prendre un mot processeur, je suis d'accord. Les deux méchanismes (taille fixe et variable) sont intéressants en fait ^^

Je vais jeter un oeil du côté des spécifications de la norme C99 (et 98 pour comparer, tant qu'on y est). Ensuite concilier tout ça avec le C++ ... Wesh, ça fait beaucoup! Et comme je suis à la bourre ce quadri, ce ne sera pas pour tout de suite :P
Je marque le lien !

Pour le TP, heu ... A première vue, faudrait plusieurs architectures/implémentations + compilos qui vont avec. A moins de virtualiser celles-ci, peut être ? Compilation croisée ?
Pas si facile que ça en fait x3

Merci pour la réponse, en tout cas ^_^