J'ai un numéro stocké comme un ulong. Je veux que les bits stockés en mémoire soient interprétés en complément à 2. Donc, je veux que le premier bit soit le bit de signe, etc. Si je veux convertir en un long, de sorte que le nombre soit interprété correctement comme un complément à 2, comment faire?

J'ai essayé de créer des pointeurs de différents types de données qui pointaient tous vers le même tampon. J'ai ensuite stocké l'ulong dans le tampon. J'ai ensuite déréférencé un long pointeur. Cela me donne cependant un mauvais résultat?

J'ai fait :

#include <iostream>
using namespace std;

int main() {
    unsigned char converter_buffer[4];//  

    unsigned long       *pulong;
    long                *plong;


    pulong = (unsigned long*)&converter_buffer;
    plong  =  (long*)&converter_buffer;

    unsigned long ulong_num = 65535; // this has a 1 as the first bit

    *pulong = ulong_num;

    std:: cout << "the number as a long is" << *plong << std::endl;
    return 0;
}

Pour une raison quelconque, cela me donne le même nombre positif. Le casting aiderait-il?

1
Ariel Baron 20 avril 2017 à 21:12

3 réponses

Meilleure réponse

En fait, utiliser des pointeurs était un bon début, mais vous devez d'abord convertir votre unsigned long* en void*, puis vous pouvez convertir le résultat en long* et le déréférencer:

#include <iostream>
#include <climits>

int main() {
    unsigned long ulongValue = ULONG_MAX;
    long longValue = *((long*)((void*)&ulongValue));

    std::cout << "ulongValue: " << ulongValue << std::endl;
    std::cout << "longValue:  " << longValue << std::endl;

    return 0;
}

Le code ci-dessus donnera les résultats suivants:

ulongValue: 18446744073709551615
longValue:  -1

Avec les modèles, vous pouvez le rendre plus lisible dans votre code:

#include <iostream>
#include <climits>

template<typename T, typename U>
T unsafe_cast(const U& from) {
    return *((T*)((void*)&from));
}

int main() {
    unsigned long ulongValue = ULONG_MAX;
    long longValue = unsafe_cast<long>(ulongValue);

    std::cout << "ulongValue: " << ulongValue << std::endl;
    std::cout << "longValue:  " << longValue << std::endl;

    return 0;
}

Gardez à l'esprit que cette solution n'est absolument pas sûre car vous pouvez caster n'importe quoi vers void*. Cette pratique était courante en C mais je ne recommande pas de l'utiliser en C ++. Considérez les cas suivants:

#include <iostream>

template<typename T, typename U>
T unsafe_cast(const U& from) {
    return *((T*)((void*)&from));
}

int main() {
    std::cout << std::hex << std::showbase;

    float fValue = 3.14;
    int iValue = unsafe_cast<int>(fValue); // OK, they have same size.

    std::cout << "Hexadecimal representation of " << fValue
              << " is: " << iValue << std::endl;
    std::cout << "Converting back to float results: "
              << unsafe_cast<float>(iValue) << std::endl;

    double dValue = 3.1415926535;
    int lossyValue = unsafe_cast<int>(dValue); // Bad, they have different size.

    std::cout << "Lossy hexadecimal representation of " << dValue
              << " is: " << lossyValue << std::endl;
    std::cout << "Converting back to double results: "
              << unsafe_cast<double>(lossyValue) << std::endl;

    return 0;
}

Le code ci-dessus résulte pour moi de ce qui suit:

Hexadecimal representation of 3.14 is: 0x4048f5c3
Converting back to float results: 3.14
Lossy hexadecimal representation of 3.14159 is: 0x54411744
Converting back to double results: 6.98387e-315

Et pour la dernière ligne, vous pouvez obtenir n'importe quoi car la conversion lira les déchets de la mémoire.

Éditer

Comme lorro commenté ci-dessous, l'utilisation de memcpy() est plus sûre et peut éviter le débordement. Alors, voici une autre version de type casting qui est plus sûre:

template<typename T, typename U>
T safer_cast(const U& from) {
    T to;
    memcpy(&to, &from, (sizeof(T) > sizeof(U) ? sizeof(U) : sizeof(T)));
    return to;
}
2
Community 23 mai 2017 à 12:02

Tu peux le faire:

uint32_t u;
int32_t& s = (int32_t&) u;

Ensuite, vous pouvez utiliser s et u de manière interchangeable avec le complément de 2, par exemple:

s = -1;
std::cout << u << '\n';     // 4294967295

Dans votre question, vous posez environ 65535 mais c'est un nombre positif. Vous pourriez faire:

uint16_t u;
int16_t& s = (int16_t&) u;

u = 65535;
std::cout << s << '\n';    // -1

Notez qu'affecter 65535 (un nombre positif) à int16_t entraînerait un comportement défini par l'implémentation, cela ne donne pas nécessairement -1.

Le problème avec votre code d'origine est qu'il n'est pas permis d'aliaser un tampon char comme long. (Et que vous pourriez déborder votre tampon). Cependant, il est possible d'aliaser un type entier comme son type signé / non signé correspondant.

0
M.M 20 avril 2017 à 22:45

En général, lorsque vous avez deux types arithmétiques de même taille et que vous souhaitez réinterpréter la représentation binaire de l'un en utilisant le type de l'autre, vous le faites avec un union:

#include <stdint.h>

union reinterpret_u64_d_union {
    uint64_t u64;
    double   d;
};

double
reinterpret_u64_as_double(uint64_t v)
{
    union reinterpret_u64_d_union u;
    u.u64 = v;
    return u.d;
}

Cependant, dans le cas particulier de la transformation d'un nombre non signé en un type signé de même taille (ou vice versa), vous pouvez simplement utiliser une distribution traditionnelle:

int64_t
reinterpret_u64_as_i64(uint64_t v)
{
    return (int64_t)v;
}

(Le cast n'est pas strictement requis pour [u]int64_t, mais si vous n'écrivez pas explicitement un cast et que les types entre lesquels vous convertissez sont petits, les "promotions d'entiers" peuvent être impliquées, ce qui n'est généralement pas souhaitable. )

La façon dont vous essayez de le faire enfreint les règles d'alias de pointeur et provoque un comportement indéfini.

En C ++, notez que reinterpret_cast<> ne fait pas ce que fait le union; il est identique à static_cast<> lorsqu'il est appliqué aux types arithmétiques.

En C ++, notez également que l'utilisation d'un union ci-dessus repose sur une règle du standard C de 1999 (avec corrigienda) qui n'a pas été officiellement incorporée dans la dernière norme C ++ que j'ai vérifiée; cependant, tous les compilateurs que je connais feront ce que vous attendez.

Et enfin, en C et C ++, long et unsigned long sont garantis de pouvoir représenter au moins −2,147,483,647 ... 214,7483,647 et 0 .. 4 294 967 295, respectivement. Votre programme de test a utilisé 65535, ce qui est garanti pour être représentable à la fois par long et unsigned long, donc la valeur n'aurait pas changé quoi que vous ayez fait. Eh bien, sauf si vous avez utilisé un alias de pointeur invalide et que le compilateur a décidé de faire voler les démons hors de votre nez à la place.

-1
zwol 20 avril 2017 à 18:29