2013-04-03 3 views
5

Je commençais un petit jeu avec C++ aujourd'hui et je suis tombé sur ce que je pensais être étrange, mais peut-être plus probablement en raison d'un malentendu de ma part et du manque de codage C pur récemment. Ce que je cherchais à faire à l'origine était de convertir un double en un tableau de caractères non signés. Ma compréhension était que les 64 bits du double (sizeof (double) est 8) seraient maintenant représentés comme 8 caractères de 8 bits. Pour ce faire, j'utilisais reinterpret_cast.C++. reinterpret_cast de double à unsigned char *

Voici donc du code pour convertir un tableau en tableau double, ou du moins je pensais que c'était ce qu'il faisait. Le problème était qu'il revenait 15 de strlen au lieu de 8, pourquoi je ne suis pas sûr.

double d = 0.3; 

unsigned char *c = reinterpret_cast<unsigned char*> (&d); 

std::cout << strlen((char*)c) << std::endl; 

Donc le strlen était mon premier numéro. Mais ensuite j'ai essayé ce qui suit et j'ai trouvé que ça a retourné 11, 19, 27, 35. La différence entre ces nombres est de 8 donc à un certain niveau, quelque chose de bien se passe. Mais pourquoi cela ne retourne-t-il pas 15, 15, 15, 15, (comme il revenait 15 dans le code ci-dessus).

double d = 0.3; 
double d1 = 0.3; 
double d2 = 0.3; 
double d3 = 0.3; 

unsigned char *c_d = reinterpret_cast<unsigned char*> (&d); 
unsigned char *c_d1 = reinterpret_cast<unsigned char*> (&d1); 
unsigned char *c_d2 = reinterpret_cast<unsigned char*> (&d2); 
unsigned char *c_d3 = reinterpret_cast<unsigned char*> (&d3); 

std::cout << strlen((char*)c_d) << std::endl; 
std::cout << strlen((char*)c_d1) << std::endl; 
std::cout << strlen((char*)c_d2) << std::endl; 
std::cout << strlen((char*)c_d3) << std::endl; 

J'ai donc regardé les adresses des caractères et ils le sont.

0x28fec4 
0x28fec0 
0x28febc 
0x28feb8 

Maintenant, cela fait sens, étant donné que la taille d'un unsigned char * sur mon système est de 4 octets, mais je pensais que le montant exact de la mémoire serait allouée à partir de la distribution, sinon il semble que reinterpret_cast est une jolie chose dangereuse ... En outre, si je

for (int i = 0; i < 4; ++i) { 
    double d = 0.3; 

    unsigned char *c = reinterpret_cast<unsigned char*> (&d); 

    std::cout << strlen((char*)c) << std::endl; 
} 

imprime 11, 11, 11, 11!

Donc, ce qui se passe ici, clairement la mémoire est écrasée par endroits et réinterpréter la distribution ne fonctionne pas comme je le pensais (c'est-à-dire que je l'utilise mal). Ayant utilisé des chaînes depuis longtemps en C++, parfois quand vous revenez à des tableaux de caractères bruts, vous oubliez ces choses.

Donc je suppose que c'est une question en 3 parties.

Pourquoi strlen a-t-il initialement renvoyé 15? Pourquoi les 4 appels strlen ont-ils augmenté en taille? Pourquoi la boucle a-t-elle renvoyé 11, 11, 11, 11?

Merci.

+0

_casting_ n'attribue aucun espace de stockage. Utiliser 'reinterpret_cast' dit essentiellement au compilateur:" Hey, je sais ce que je fais, faites-moi confiance. " Par conséquent, c'est à vous de connaître l'espace de stockage que ces conversions peuvent indiquer de manière valide. – Chad

+0

@Chad ce genre de réponses ma meilleure question. Comme je l'ai dit dans le commentaire pour la réponse choisie, je m'attendais à trop de casting. Je suis conscient du caractère \ 0, cependant, c'était vraiment le comportement de reinterpret_cast et ce qui m'avait fait un peu confus. –

Répondre

11

strlen travaux en parcourant le tableau qui assume les points passés const char* jusqu'à ce qu'il trouve un char avec la valeur 0. Ceci est le caractère de terminaison null qui est automatiquement ajouté à la fin de chaînes littérales. Les octets qui constituent la représentation de la valeur de votre double ne se terminent pas par un caractère nul.Le strlen va juste continuer après la fin de votre objet doublejusqu'à qu'il trouve un octet avec la valeur 0.

Considérons la chaîne littérale "Hello". En mémoire, avec un jeu de caractères d'exécution compatible ASCII, ce sera stocké sous forme les octets suivants (en hexadécimal):

48 65 6c 6c 6f 00 

strlen lirais par chacun d'eux jusqu'à ce qu'il trouve l'octet avec la valeur 0 et signaler combien octets qu'il a vu jusqu'à présent.

L'IEEE 754 double représentation de précision de 0.3 est:

3F D3 33 33 33 33 33 33 

Comme vous pouvez le voir, il n'y a pas octet de valeur 0, donc strlen juste ne saura pas quand arrêter. Quelle que soit la valeur renvoyée par la fonction, c'est probablement la distance jusqu'à ce qu'elle trouve un 0 en mémoire, mais vous avez déjà atteint un comportement indéfini et donc faire des suppositions à ce sujet est inutile.

+0

Je suppose que j'attendais trop de reinterpret_cast. Je suppose que dans mon esprit c'était la mémoire et le caractère final pour moi. Jamais vraiment traversé mon esprit ce n'est pas ce que reinterpret_cast est censé faire. Ça fait son boulot, le reste dépend de moi. –

+0

@Muckle_ewe 'reinterpret_cast' est le plus dangereux des lancers. Il dit, "croyez-moi, compilateur, je veux que vous traitiez cela comme un type différent". –

6

Votre problème est l'utilisation de strlen((char*)c), car strlen attend un pointeur vers une chaîne de caractères terminée par un caractère nul.

Il semble que vous attendiez une sorte de "limite" entre le 8ème et le 9ème octet, puisque ces 8 premiers octets étaient à l'origine un double.

Cette information est perdue une fois que vous avez transtypé cette mémoire en char*. Il est de la responsabilité de votre code de savoir combien de char sont valides.

2

Quelques choses:

  1. sizeof(double) est probablement pas 4. Il est généralement 8. Utilisez l'opérateur au lieu d'une prise en charge codée en dur.
  2. Le pointeur reinterpret_cast<unsigned char*>(&d) n'indique pas un pointeur vers une "chaîne" terminée par un caractère nul. strlen fonctionne en itérant jusqu'à ce qu'il trouve un null. Vous êtes dans un comportement indéfini là-bas.