بتات تعويم ومستعار صارم
-
29-09-2019 - |
سؤال
أحاول استخراج البتات من تعويم دون استدعاء سلوك غير محدد. هنا هو محاولة لقائي الاول:
unsigned foo(float x)
{
unsigned* u = (unsigned*)&x;
return *u;
}
كما أفهمها ، هذا ليس مضمونًا للعمل بسبب قواعد التعاون الصارمة ، أليس كذلك؟ هل تعمل إذا اتخذت خطوة وسيطة مع مؤشر حرف؟
unsigned bar(float x)
{
char* c = (char*)&x;
unsigned* u = (unsigned*)c;
return *u;
}
أو هل يجب علي استخراج البايت الفردي بنفسي؟
unsigned baz(float x)
{
unsigned char* c = (unsigned char*)&x;
return c[0] | c[1] << 8 | c[2] << 16 | c[3] << 24;
}
بالطبع هذا له عيب اعتمادًا على endianness ، لكن يمكنني العيش مع ذلك.
الاختراق الاتحاد هو بالتأكيد سلوك غير محدد ، أليس كذلك؟
unsigned uni(float x)
{
union { float f; unsigned u; };
f = x;
return u;
}
فقط للاكتمال ، إليك نسخة مرجعية من foo
. أيضا سلوك غير محدد ، أليس كذلك؟
unsigned ref(float x)
{
return (unsigned&)x;
}
لذلك ، هل من الممكن استخراج البتات من تعويم (على افتراض أن كلاهما عرض 32 بت, ، بالطبع)؟
تحرير: وهنا هو memcpy
النسخة كما اقترحت Goz. نظرًا لأن العديد من المجمعين لا يدعمون static_assert
ومع ذلك ، فقد استبدلت static_assert
مع بعض القوالب metaprogramming:
template <bool, typename T>
struct requirement;
template <typename T>
struct requirement<true, T>
{
typedef T type;
};
unsigned bits(float x)
{
requirement<sizeof(unsigned)==sizeof(float), unsigned>::type u;
memcpy(&u, &x, sizeof u);
return u;
}
المحلول
حول الطريقة الوحيدة لتجنب أي مشكلات حقًا هي memcpy.
unsigned int FloatToInt( float f )
{
static_assert( sizeof( float ) == sizeof( unsigned int ), "Sizes must match" );
unsigned int ret;
memcpy( &ret, &f, sizeof( float ) );
return ret;
}
نظرًا لأنك تقوم بتعليق المبلغ الثابت ، فإن المترجم سيقوم بتحسينه.
ومع ذلك ، فإن طريقة الاتحاد مدعومة على نطاق واسع.
نصائح أخرى
الاختراق الاتحاد هو بالتأكيد سلوك غير محدد ، أليس كذلك؟
نعم و لا. وفقًا للمعيار ، إنه بالتأكيد سلوك غير محدد. لكن هذه خدعة شائعة الاستخدام ، حيث يضمن كل من برنامج التحليل البرمجي الشهير ، كل مترجم شهير آخر ، أن يكون آمنًا وسيعمل كما هو متوقع.
ما يلي لا ينتهك قاعدة التعرجات ، لأنه لا يوجد استخدام لسرات الوسيلة التي تصل إلى أنواع مختلفة في أي مكان
template<typename B, typename A>
B noalias_cast(A a) {
union N {
A a;
B b;
N(A a):a(a) { }
};
return N(a).b;
}
unsigned bar(float x) {
return noalias_cast<unsigned>(x);
}
إذا كنت تريد حقًا أن تكون غير ملحق بشأن حجم نوع الطوائف وإرجاع البتات الخام فقط ، فقم بعمل شيء كهذا:
void float_to_bytes(char *buffer, float f) {
union {
float x;
char b[sizeof(float)];
};
x = f;
memcpy(buffer, b, sizeof(float));
}
ثم نسميها مثل ذلك:
float a = 12345.6789;
char buffer[sizeof(float)];
float_to_bytes(buffer, a);
ستنتج هذه التقنية ، بطبيعة الحال ، الإخراج الخاص بترتيب بايت جهازك.