Je suis confus avec size_t
en C. Je sais qu'il est retourné par l'opérateur sizeof
. mais qu'est ce que c'est exactement? Est-ce un type de données?
Disons que j'ai une boucle for
:
for(i = 0; i < some_size; i++)
Devrais-je utiliser int i;
ou size_t i;
?
Selon la norme ISO C de 1999 (C99),
size_t
est un entier non signé type d'au moins 16 bits (voir les sections 7.17 et 7.18.3).
size_t
est un type de données non signé défini par plusieurs normes C/C++, par exemple. la norme C99 ISO/IEC 9899, qui est défini dansstddef.h
. 1 It can être importé par l’inclusion destdlib.h
comme ce fichier en interne sous comprendstddef.h
.Ce type est utilisé pour représenter le taille d'un objet. Fonctions de la bibliothèque que les tailles de reprise ou de retour les attendent être de type ou avoir le type de retour de
size_t
. En outre, le plus fréquemment utilisé basé sur le compilateur La taille de l'opérateur doit être évaluée à valeur constante compatible avecsize_t
.
En conséquence, size_t
est un type garanti de pouvoir contenir n'importe quel index de tableau.
size_t
est un type non signé. Ainsi, il ne peut représenter aucune valeur négative (<0). Vous l'utilisez lorsque vous comptez quelque chose et êtes certain que cela ne peut pas être négatif. Par exemple, strlen()
renvoie un size_t
car la longueur d'une chaîne doit être au moins égale à 0.
Dans votre exemple, si votre index de boucle doit toujours être supérieur à 0, il peut être judicieux d'utiliser size_t
ou tout autre type de données non signé.
Lorsque vous utilisez un objet size_t
, vous devez vous assurer que, dans tous les contextes, y compris l'arithmétique, vous souhaitez des valeurs non négatives. Par exemple, supposons que vous ayez:
size_t s1 = strlen(str1);
size_t s2 = strlen(str2);
et vous voulez trouver la différence entre les longueurs de str2
et str1
. Tu ne peux pas faire:
int diff = s2 - s1; /* bad */
En effet, la valeur affectée à diff
sera toujours un nombre positif, même lorsque s2 < s1
, car le calcul est effectué avec des types non signés. Dans ce cas, selon votre cas d'utilisation, il vaudrait peut-être mieux utiliser int
(ou long long
) pour s1
et s2
.
Certaines fonctions dans C/POSIX pourraient/devraient utiliser size_t
, mais ne le font pas pour des raisons historiques. Par exemple, le deuxième paramètre à fgets
devrait idéalement être size_t
, mais est int
.
size_t
est un type pouvant contenir n'importe quel index de tableau.
Selon l’implémentation, il peut s’agir de:
unsigned char
unsigned short
unsigned int
unsigned long
unsigned long long
Voici comment size_t
est défini dans stddef.h
de ma machine:
typedef unsigned long size_t;
Si vous êtes du type empirique,
echo | gcc -E -xc -include 'stddef.h' - | grep size_t
Sortie pour Ubuntu 14.04 64 bits GCC 4.8:
typedef long unsigned int size_t;
Notez que stddef.h
est fourni par GCC et non pas glibc sous src/gcc/ginclude/stddef.h
dans GCC 4.2.
Apparences intéressantes pour C99
malloc
prend size_t
en argument, il détermine donc la taille maximale pouvant être allouée.
Et comme il est également retourné par sizeof
, je pense que cela limite la taille maximale de tout tableau.
Voir aussi: Quelle est la taille maximale d'un tableau en C?
La page de manuel de types.h dit:
size_t doit être un type entier non signé
Comme personne ne l’a encore mentionné, la principale signification linguistique de size_t
est que l’opérateur sizeof
renvoie une valeur de ce type. De même, la signification principale de ptrdiff_t
est que soustraire un pointeur à un autre produira une valeur de ce type. Les fonctions de bibliothèque qui l'acceptent le font car cela leur permettra de fonctionner avec des objets de taille supérieure à UINT_MAX sur des systèmes où de tels objets pourraient exister, sans forcer les appelants à gaspiller du code en transmettant une valeur supérieure à "unsigned int" sur des systèmes suffirait pour tous les objets possibles.
size_t
et int
ne sont pas interchangeables. Par exemple, sur Linux 64 bits, size_t
a une taille de 64 bits (c'est-à-dire sizeof(void*)
) mais int
est de 32 bits.
Notez également que size_t
est non signé. Si vous avez besoin d'une version signée, il existe ssize_t
sur certaines plates-formes et ce serait plus pertinent pour votre exemple.
En règle générale, je suggérerais d'utiliser int
pour la plupart des cas généraux et de n'utiliser que size_t
/ssize_t
lorsqu'il en a un besoin spécifique (avec mmap()
par exemple).
Pour expliquer pourquoi size_t
devait exister et comment nous en sommes arrivés là:
En termes pragmatiques, size_t
et ptrdiff_t
ont une largeur de 64 bits sur une implémentation de 64 bits, une largeur de 32 bits sur une implémentation de 32 bits, etc. Ils ne pouvaient forcer aucun type existant à signifier cela, sur chaque compilateur, sans casser le code hérité.
Un size_t
ou ptrdiff_t
n'est pas nécessairement identique à un intptr_t
ou uintptr_t
. Elles étaient différentes sur certaines architectures qui étaient encore utilisées lorsque size_t
et ptrdiff_t
ont été ajoutées à la norme à la fin des années 80 et qui sont devenues obsolètes lorsque C99 a ajouté de nombreux nouveaux types mais n’est pas encore parti (comme Windows 16 bits). Le x86 en mode protégé 16 bits disposait d'une mémoire segmentée dans laquelle le tableau ou la structure la plus grande possible ne pouvait contenir que 65 536 octets, mais un pointeur far
devait être large de 32 bits, plus large que les registres. Sur ceux-ci, intptr_t
aurait été large de 32 bits, mais size_t
et ptrdiff_t
pourraient avoir une largeur de 16 bits et entrer dans un registre. Et qui savait quel type de système d'exploitation pourrait être écrit à l'avenir? En théorie, l'architecture i386 propose un modèle de segmentation 32 bits avec des pointeurs 48 bits qu'aucun système d'exploitation n'a encore utilisé.
Le type d'un décalage de mémoire ne peut pas être long
car beaucoup trop de code hérité suppose que long
a exactement 32 bits de large. Cette hypothèse a même été intégrée dans les API UNIX et Windows. Malheureusement, beaucoup d'autres codes hérités supposaient également qu'une long
était suffisamment large pour contenir un pointeur, un décalage de fichier, le nombre de secondes écoulées depuis 1970, etc. POSIX fournit maintenant un moyen standardisé d’imposer la dernière hypothèse à la place de la première, mais ce n’est pas non plus une hypothèse transférable.
Cela ne peut pas être int
car seule une infime poignée de compilateurs dans les années 90 a int
une largeur de 64 bits. Ensuite, ils sont devenus vraiment bizarres en maintenant long
32 bits de large. La prochaine révision de la norme a déclaré illégal que int
soit plus large que long
, mais int
a toujours une largeur de 32 bits sur la plupart des systèmes 64 bits.
Il ne pouvait pas s'agir de long long int
, qui de toute façon a été ajouté ultérieurement, car sa largeur maximale était de 64 bits, même sur les systèmes 32 bits.
Donc, un nouveau type était nécessaire. Même si ce n'était pas le cas, tous ces autres types signifiaient autre chose qu'un décalage dans un tableau ou un objet. Et s'il y avait une leçon à tirer du fiasco de la migration de 32 à 64 bits, il fallait préciser les propriétés qu'un type devait avoir, et ne pas en utiliser une qui signifiait différentes choses dans différents programmes.
En général, si vous commencez à 0 et montez, utilisez toujours un type non signé pour éviter un débordement, ce qui vous placerait dans une situation de valeur négative. Ceci est extrêmement important, car s'il se trouve que les limites de votre tableau sont inférieures au maximum de votre boucle, mais que votre maximum de boucle se trouve supérieur au maximum de votre type, vous obtiendrez un négatif négatif et vous pourriez rencontrer une erreur de segmentation (SIGSEGV). Donc, en général, n'utilisez jamais int pour une boucle commençant à 0 et allant vers le haut. Utilisez un non signé.
size_t est un type de données entier non signé. Sur les systèmes utilisant la bibliothèque GNU C, ce sera unsigned int ou unsigned long int. size_t est couramment utilisé pour l'indexation de tableaux et le comptage de boucles.
size_t ou tout type non signé peut être vu utilisé comme variable de boucle car les variables de boucle sont généralement supérieures ou égales à 0.
Lorsque nous utilisons un objet size_t , nous devons nous assurer que, dans tous les contextes, y compris l'arithmétique, nous voulons uniquement des valeurs non négatives. Par exemple, le programme suivant donnerait certainement le résultat inattendu:
// C program to demonstrate that size_t or
// any unsigned int type should be used
// carefully when used in a loop
#include<stdio.h>
int main()
{
const size_t N = 10;
int a[N];
// This is fine
for (size_t n = 0; n < N; ++n)
a[n] = n;
// But reverse cycles are tricky for unsigned
// types as can lead to infinite loop
for (size_t n = N-1; n >= 0; --n)
printf("%d ", a[n]);
}
Output
Infinite loop and then segmentation fault