Type casting без изменения содержимого
int i;
i=*(int *)&f;
Несмотря на длинное выражение, мой борландовский компилятор компилирует это в одну команду mov, как и надо.
Но как быть, если я хочу присвоить константное значение:
После выполнения этого кода в переменной i всё-равно будет значение 1 (целое), а я бы хотел 0x3f800000 - внутреннее представление единицы в формате float. Неужели в таком мощном языке нет средств для такой простой вещи?
И попутно по поводу инициализации union:
{
int i;
float f;
}u={1};
Этот код присвоит единицу типу int, иначе говоря первому объявленному члену. А можно ли таким образом инициализировать тип float, т.е. не первый объявленный член?
Насчет первого походу нет.
А насчет второго
{
int i;
float f;
}u={1};
Этот код присвоит единицу типу int, иначе говоря первому объявленному члену. А можно ли таким образом инициализировать тип float, т.е. не первый объявленный член?
еще чище)
Походу С99 должен подерживать именованые инициализаторы, помоему это так называеться, тоесть должен работать следующий код
{
int i;
float f;
}u={.f = 1};
Но как насчет C++ непомню) Builder ругаеться Другое не пробывал) Завтра уж) Спать пора)))
в си можно рискнуть так -
sscanf("1","%f",&i2);
но асм кода будет больше чем в твоем примере
а асм вставки в код неподходят?
//+add
да уж, ночь дает о себе знать...
какие асм вставки, какие sscanf....
хочешь константу так используй константу
Всё правильно - ведь манипуляции с адресами нет. По-этому сначала 1 приводится к типу float, а потом - к типу int.
Код:
union
{
int i;
float f;
}u={.f = 1};
Есть такое, только в C++ это не работает. Опять напрашивается вопрос, неужели такую возможность выкинули и не придумали никакой замены? Я сколько не искал, ничего не нашёл :-(
в си можно рискнуть так -
Код:
int i2;
sscanf("1","%f",&i2);
но асм кода будет больше чем в твоем примере
Не имеет смысла вызывать такую сложную функцию (в плане производительности), чтобы просто скопировать 4 байта, которые компилятор и так представляет в нужном виде.
хочешь константу так используй константу
Код:
i = 0x3f800000L;
Асм вставки не подходят, т.к. хочется использовать именно возможности синтаксиса C++. А насчёт константы, так ведь в том и дело, чтобы не "вручную" высчитывать её, а предоставить это компилятору. Кстати, для асма тут вообще нет никаких проблем с типами:
и используй дальше dword ptr где хочешь. Кстати, вот такой код
f=1;
после компиляции будет выглядить примерно так:
Т.е. компилятор в целях оптимизации всё равно внутри представляет вещественные числа как целые. Логично, что всё таки должен был бы быть какой-то способ для решения поставленной выше задачи.
Код:
int i = *((int*)(&((const float&)1)));
Честно говоря, не совсем понял. ((const float&)1) (или скорей всего так: ((const float&)1.0) ) - уже никак (Can not cast from int/double to const float &). Вообще, амперсанд перед константой никак не поставить (Must take address of a memory location). Может есть другие способы?
Мою проблему решил бы ответ на любой из двух вопросов, может кто-то всё-таки знает, как быть с union'ом?
f=1;
после компиляции будет выглядить примерно так:
Т.е. компилятор в целях оптимизации всё равно внутри представляет вещественные числа как целые. Логично, что всё таки должен был бы быть какой-то способ для решения поставленной выше задачи.
Кстати говоря, неверно. Числа с плавающей запятой на процессорах x86 не хранятся в регистрах общего назначения. Поэтому не храняться как чаcть команд типа mov. Сами константы лежат в памяти и загружаются оттуда в стек FPU.
Честно говоря, не совсем понял. ((const float&)1) (или скорей всего так: ((const float&)1.0) ) - уже никак (Can not cast from int/double to const float &). Вообще, амперсанд перед константой никак не поставить (Must take address of a memory location). Может есть другие способы?
Мою проблему решил бы ответ на любой из двух вопросов, может кто-то всё-таки знает, как быть с union'ом?
Чего тут понимать? Сначала получаем ссылку на константу типа float, потом получаем ее адрес, затем переводим в указатель типа int, разименовав его получаем то, что нужно.
Код получается примерно следующий:
mov eax, DWORD PTR $T547[ebp]
mov DWORD PTR ?i@@3HA, eax
Ну напиши так:
и тогда не важно как ты пишешь... 1, 1.0, 1.f или еще как.
Если пугает огромная конструкция, то пишем define:
int i = float2int(1.0);
В стек FPU числа из памяти загружаются только непосредственно перед выполнением арифметической операции, а до этого они могут храниться где угодно. Согласен, пример не совсем удачный (лучше было бы, например, mov dword ptr [ebp-8h],3f800000h, но суть не меняется), но и такая ситуация возможна, например, если эта переменная не используется непосредственно в арифметических операциях в контексте функции, где она объявлена, а только передаётся другим функциям (не обязательно __fastcall, push edx ничем не хуже) в качестве аргумента. :p
Я же говорю, у меня этот код не работает (Can not cast from 'float' to 'const float &'), хотя у меня складывается впечатление, что у уважаемого тов. Nixus'а всё прекрасно компилируется. В чём может быть дело?
а насчет union, так не пробывал ?
{
union
{
int i;
float f;
};
foo(int val):f((float)val){}
};
но за результат не ручаюсь, счас попробывать негде
А какой у вас компилятор? C++? У меня Borland C++ 5.2
{
union
{
int i;
float f;
};
foo(int val):f((float)val){}
};
Спасибо, мысль понял. Только одна неточность, в конструктор лучше передавать параметр типа float, потому что, иначе он сначала урезается до целой части, а потом присваивается члену f, и код
приведёт к тому, что в f будет 3.0.
Вот моя версия этого кода:
{
union
{
int i;
float f;
};
foo(float val,bool wantfloat){if (wantfloat) f=val;else i=val;}
}myfoo(3.14,1);
Можно сказать, что вопрос с union'ом решён (хотя здесь и вызывается коструктор, так можно и правда sscanf() использовать), ну а если у всех кроме меня :( ещё и компилируется вышеприведённое безобразие, то, наверное, все ответы даны. Всем за них большое спасибо!
Возможно все. Но приведенный вами пример был не верен. Я это показал.
В MSVC++ 6.0 все компилиться и без предупреждений даже.
Можно сделать к примеру вот так
*((float*)&i) = 1;
Но это уже опаснее, т.к. на некоторых машинах sizeof(int). может быть меньше sizeof(float).
Действительно, в MSVC++ работает. А чё у них стандарты разные что ли?
*((float*)&i) = 1;
Такой способ мне не подходит.
Пожалуй, объясню, зачем мне всё это. У меня есть структура, в которой имеется член типа unsigned int, и в котором физически могут хранится и целые, и вещественные числа, и указатели на строки и что угодно :-). Вот фрагмент моего кода:
//variable flags and types
#define u_varf_readonly 0x100
#define u_varf_user 0x200
#define u_vart_boolean 0x1
#define u_vart_int 0x2
#define u_vart_float 0x3
#define u_vart_string 0x4
typedef struct uvar_t *puvar_t;
typedef bool (*uvarparser_t)(puvar_t pvar,dword value,dword operation,pointer param);//ret false to execute standart action
//variables parser operations
#define uvar_parserop_read 1//(dword *)param - pointer to buffer for value storing
#define uvar_parserop_write 2
#define uvar_parserop_str 3//(char *)param - pointer to buffer for string
#define uvar_parserop_val 4//(char *)value - string,(dword *)param - buffer for value
#define uvar_parserop_remove 5
#define uvar_maxname 32
typedef struct uvar_t
{
dword value;//32-bit value can be any of the types listed above
char name[uvar_maxname];
dword flags;//high-order word can be used by user
uvarparser_t parser;
puvar_t prev,next;
}*puvar_t;
#define uVarFloat(x) (*(float *)&x)
#define uVarDword(x) (*(dword *)&x)
.....
... и далее в приложении, например, так:
....
uVarRegister(&screen_showfps);
Всего две простые строки и в консоли появляется новая переменная, причём её текущее значение всегда доступно по screen_showfps.value, которое без труда можно привести к любому типу. Как видите, мне важна максимальная простота инициализации. Когда дело дошло до значений типа float, оказалось, что не всё так просто и пришлось делать так:
uvar_t md_timecomp={uVarDword(md_f1),"md_timecompression",u_vart_float};
создавать для этих целей отдельную переменную не очень удобно и громоздко. Предложенный Nixus'ом вариант подошёл бы, если бы работал в Борланде, но увы...:(
{
union
{
int i;
float f;
};
foo (int val) {i=val;}
foo (float val) {f=val;}
}myfoo_int(1),myfoo_float(1.0f);
Адаптировать это под мою задачу - дело техники. Всем огромное спасибо!!
{
union
{
int i;
float f;
} data;
float_and_int(int i)
{
data.i = i;
};
float_and_int(float f)
{
data.f = f;
}
template<typename T>
const T get ()
{
terminate(); // Если вдруг параметр шаблона T - ни int, ни float
}
template<>
const int get<int> ()
{
return data.i;
}
template<>
const float get<float> ()
{
return data.f;
}
}
void main (void)
{
float_and_int fi (1.0);
cout << fi.get<int>();
};
Там я написал функцию super_cast:
T super_cast(const U& u) {
return *(T*)&u;
}
Используется так же как и любые др. cast-ы:
Для начала я решил понять, почему код Nixus'a
нормально компилируется под MSVC++ и не компилится совсем под Борландом. А для этого посмотрел как выглядит код
f=2;//внутреннее представление двойки 0x40000000
после этих компиляторов. MSVC++ 8.0 выдал это:
fstp dword ptr [f]
а Борланд 5.2 ответил вот этим:
В принципе, для людей знающих здесь всё ясно, Microsoft однозначно в глубокой...отдыхает он, в общем. Для остальных поясню: Борланду для выполнения этой задачи понадобилась одна простая команда CPU (и это правильно), MSVC же делает два обращения к FPU (а каждое такое обращение это уже операция повышенной сложности, которая отнимает тактов намного больше, чем просто команда CPU) и к тому же, для константы '2' (которая и используется-то всего один раз) отводит 4 байта в сегменте данных. А ведь надо всего-то по указанному адресу положить нужные 4 байта, FPU не для этого был придуман. Именно поэтому под MSVC возможно делать адресацию константы, ведь она постоянно хранится в памяти, делать ту манипуляцию с адресами, а в Борланде такой номер не пройдёт, даже переменной типа double константа будет присваиваться за две команды CPU, младшая часть, потом старшая, и я с ними полностью согласен. Под Борланд ассемблерный код этого куска:
i=0x40000000;
будет выглядеть точно также, как и предыдущий пример. Именно поэтому у меня и напросился мой первый вопрос, который я сформулировал, наверно не совсем правильно, надо было так: "хочу константу типа int, которая является внутренним представлением константы x типа float" :-) Причём, непременно, в Борланде. Код
*((float*)&i) = 1;
конечно же даст нужный результат, но константа как была типа float, так с ним и осталась, манипуляции проходят с типом переменной и инициализировать структуру таким кодом я не смогу (в смысле методом ..={...}, конечно).
А как вам такой код:
float f;
f=*((float*)(&((const int&)2)));
...получаем:
0041137E mov dword ptr [ebp-0C8h],2
00411388 fld dword ptr [ebp-0C8h]
0041138E fstp dword ptr [f (417178h)] ;теперь у нас в dword ptr [f] находится 0x00000002, зашибись
Увидев это чудо, программисты-системщики пойдут с транспорантами сжигать чучело Билла Гейтса у стен американского посльства.
Там я написал функцию super_cast:
Код:
template<typename T, typename U>
T super_cast(const U& u) {
return *(T*)&u;
}
Используется так же как и любые др. cast-ы:
Код:
int i = super_cast<int>(1.f)
Я не подразумевал использование вызовов каких-либо функций. Взгляните на код:
lea edx,[ebp-0x04]
push edx
call super_cast; а там ещё куча всякого безобразия
pop ecx
mov ,eax; и в eax, естественно, всё тот же 0x3f800000, круто :-)
Ну и если до конца добить этот пример, то можно сказать, что внутри функции super_cast меняется тип переменной, а не константы (аргумент функции - всё те же 4 байта в стековом кадре, ничем не хуже любой другой локальной переменной, на которую можно получить указатель).
Ну и если подытожить, то, судя по всему, на мой первый вопрос в природе нет ответа. Зато, теперь наверное, на всю жизнь запомню, как выглядит единица изнутри :)
f=2;//внутреннее представление двойки 0x40000000
после этих компиляторов. MSVC++ 8.0 выдал это:
fstp dword ptr [f]
а Борланд 5.2 ответил вот этим:
А у меня MSVC++ выдал
; 12 : float f;
; 13 : f=2;
mov DWORD PTR _f$[ebp], 1073741824 ; 40000000H
Причем и в Debug и в Release. Однако странно.
Во-первых, различия в версии - последние мсвц оптимизируют гораздо лучше, при всем этом гораздо лучше отвечают стандарту. (ИМХО).
Во-вторых, и особенно это касается vagran, если уж речь идет о константах, так и надо писать:
Уверен, здесь не только будет использоваться CPU вместо FPU, но данные вообще будут храниться в регисте вместо памяти.
Хотя, кто знает, нужно проверить...
Уточняю (так, между прочим, информация к размышлению): согласно стандарта С++, существуют cv-квалификаторы, const означает неизменность данных вообще, volatile (применяется по умолчанию) - модифицируемость данных вообще, а есть еще const volatile - неизменность данных со стороны данного потока, учитывая то, что данные могут измениться параллельно исполняющимися потоками/процессами.
Не указав квалификатора (а значит - volatile), вы вынуждаете компилятор не применять практически никакой оптимизации, ибо данные могут измениться другим потоком, и поэтому при каждом обращении к ним со стороны текущего потока нужно считывать их из памяти.
int i = *((int*)(&((const float&)(float)1.0)));
У меня это в ДОСовском Borland C++ не компилируется. Но, мо-моему, есть способ проще, который, кстати, и в Pascal'е можно использовать:
*(float*)&i=1.0;