web-dev-qa-db-fra.com

Quelle est la bonne façon d’utiliser printf pour imprimer un clock_t?

J'utilise actuellement une conversion explicite en unsigned long long et en utilisant %llu pour l'imprimer, mais puisque size_t a le spécificateur %z, pourquoi clock_t n'en a-t-il pas?

Il n'y a même pas de macro pour cela. Je peux peut-être supposer que sur un système x64 (système d'exploitation et processeur), size_t a une longueur de 8 octets (et même dans ce cas, ils ont fourni %z), mais qu'en est-il de clock_t?

29
Spidey

Il semble y avoir aucun moyen parfait. La racine du problème est que clock_t peut être un entier ou une virgule flottante.

clock_t peut être un type à virgule flottante

Comme Bastien Léonard mentionne pour POSIX (le vote se lève), C99 N1256 draft 7.23.1/3 dit aussi que:

[clock_t est] des types arithmétiques capables de représenter des temps

et 6.2.5/18:

Les types entier et flottant sont collectivement appelés types arithmétiques.

et la norme définit le type arithmétique comme des types entiers ou à virgule flottante.

Si vous voulez diviser par CLOCKS_PER_SEC, utilisez long double

La valeur de retour de clock() est définie par l’implémentation. Le seul moyen d’en extraire la signification standard consiste à diviser par CLOCKS_PER_SEC pour trouver le nombre de secondes:

clock_t t0 = clock();
/* Work. */
clock_t t1 = clock();
printf("%Lf", (long double)(t1 - t0));

C'est assez bon, bien que pas parfait, pour les deux raisons suivantes:

  • il ne semble exister aucun analogue à intmax_t pour les types à virgule flottante: Comment obtenir le type de données à virgule flottante avec la plus grande précision et son spécificateur printf? Donc si un type plus grand en virgule flottante sort demain, il pourrait être utilisé et casser votre implémentation.

  • si clock_t est un entier, la conversion en float est bien définie pour utiliser le float le plus proche possible. Vous perdrez peut-être en précision, mais cela n’aurait pas beaucoup d’importance par rapport à la valeur absolue, et cela ne se produirait que très longtemps, par exemple. long int en x86 est le nombre flottant de 80 bits avec une valeur significative de 64 bits, ce qui correspond à des millions d'années en secondes.

Go upvote lemonad qui a dit quelque chose de similaire.

Si vous supposez que c'est un entier, utilisez% ju et uintmax_t

Bien que unsigned long long soit actuellement le type d’entier standard le plus grand possible:

il est donc préférable de définir le plus grand type d'entier non signé possible:

#include <stdint.h>

printf("%ju", (uintmax_t)(clock_t)1);

uintmax_t est garanti pour avoir la taille du plus grand nombre entier possible sur la machine.

uintmax_t et son spécificateur printf %ju ont été introduits dans c99 et gcc par exemple les implémente.

En prime, cela résout une fois pour toutes la question de savoir comment utiliser de manière fiable les types entiers printf (ce qui n’est malheureusement pas nécessairement le cas pour clock_t).

Qu'est-ce qui pourrait mal tourner si c'était un double:

  • si trop grand pour tenir dans l'entier, comportement indéfini
  • beaucoup plus petit que 1, sera arrondi à 0 et vous ne verrez rien

Étant donné que ces conséquences sont beaucoup plus sévères que la conversion de nombre entier en float, l’utilisation de float est probablement une meilleure idée.

Sur la glibc 2.21 c'est un entier

Le manuel dit que double est une meilleure idée:

Sur les systèmes GNU/Linux et GNU/Hurd, clock_t est équivalent à long int et CLOCKS_PER_SEC est une valeur entière. Mais dans d’autres systèmes, clock_t et la macro CLOCKS_PER_SEC peuvent être de type entier ou à virgule flottante. En doublant les valeurs de temps CPU, comme dans l'exemple ci-dessus, vous vous assurez que des opérations telles que l'arithmétique et l'impression fonctionnent correctement et de manière cohérente, quelle que soit la représentation sous-jacente. 

En glibc 2.21:

Voir également

C'est probablement parce que les tics d'horloge ne sont pas une unité très bien définie. Vous pouvez le convertir en secondes et l’imprimer en double:

time_in_seconds = (double)time_in_clock_ticks / (double)CLOCKS_PER_SEC;
printf("%g seconds", seconds);

La macro CLOCKS_PER_SEC se développe en une expression représentant le nombre de ticks d'horloge par seconde.

11
lemonad

Autant que je sache, votre façon de faire est la meilleure. Sauf que clock_t peut être un type réel:

time_t et clock_t doivent être des types entiers ou réels.

http://www.opengroup.org/onlinepubs/009695399/basedefs/sys/types.h.html

11
Bastien Léonard

La norme C doit s'adapter à une grande variété d'architectures, ce qui rend impossible toute garantie supplémentaire en dehors du fait que le type d'horloge interne est arithmétique.

Dans la plupart des cas, les intervalles de temps vous intéressent, je convertirais donc la différence en ticks d'horloge en millisecondes. Un unsigned long est assez grand pour représenter un intervalle de près de 50 jours, même s'il est 32 bits, il devrait donc être assez grand pour la plupart des cas:

clock_t start;
clock_t end;
unsigned long millis = (end - start) * 1000 / CLOCKS_PER_SEC;
3
Christoph

Une solution consiste à utiliser la fonction gettimeofday. On peut trouver la différence en utilisant cette fonction:

unsigned long  diff(struct timeval second, struct timeval first)
{
    struct timeval  lapsed;
    struct timezone tzp;
    unsigned long t;

    if (first.tv_usec > second.tv_usec) {
        second.tv_usec += 1000000;
        second.tv_sec--;
    }

    lapsed.tv_usec = second.tv_usec - first.tv_usec;
    lapsed.tv_sec  = second.tv_sec  - first.tv_sec;
    t = lapsed.tv_sec*1000000 + lapsed.tv_usec;

    printf("%lu,%lu - %lu,%lu = %ld,%ld\n",
           second.tv_sec, second.tv_usec,
           first.tv_sec,  first.tv_usec,
           lapsed.tv_sec, lapsed.tv_usec);

    return t;
}
1
rani