LINUX.ORG.RU

cast unsigned int * to unsigned short *

 , , ,


0

2

в общем, пишу так

buf = htonh(*((uint16_t *)p + i));

но это не очень читабельно, хочу как-то так:

buf = htonh((uint16_t[])p[i]);

подскажите, как правильно написать? не могу ничего нагуглить

★★★

Последнее исправление: IvanR (всего исправлений: 1)

Ответ на: комментарий от anonymous

6.5 Expressions:

http://port70.net/~nsz/c/c11/n1570.html#6.5p7

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:88)

   * a type compatible with the effective type of the object,
   * a qualified version of a type compatible with the effective type of the object,
   * a type that is the signed or unsigned type corresponding to the effective type of the object,
   * a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
   * an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
   * a character type. 

Насколько я понимаю, именно этот список разрешает нам кастить всё что угодно к char*. И в нём я не вижу ничего, что разрешало бы кастить unsigned int* в unsigned short*

fsb4000 ★★★★★
()
Ответ на: комментарий от x86-

uint16_t buf = htonh(*((uint16_t *)(p + i)));

так лучше не нада.

индексируется по типу указателя, соотвественно сначала указатель надо приводить к верному типу, а потом индексировать.

alysnix ★★★
()
Ответ на: комментарий от x86-

Короче зря бубнили друг на друга. Гыгыгы. Главное что-бы ТС скобочки проставил. А лучше на разные строки вывел всё с буферными переменными. А то вот так и будет там не заметил, тут подзабыл. А потом ломай голову ЧЁНИТАК ТО! )))

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от x86-

по практическому опыту все отлично работает

до следующего обновления компилятора. В котором перестанет. И будут виниться разработчики компилятора…

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Тебя послушать, так и в юнионе нельзя писать в один член, а читать из другого (когда они достаточно несовместимых типов).

anonymous
()
Ответ на: комментарий от alysnix

Тут p уже приведён. Смещение по i будет правильным. Но вот далее каст к указателю на uint16_t просто не нужен. Нужно просто разыменовать звёздочкой что-бы получить значение и всё.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от anonymous

Зависит от наличия оптимизации -O2. Только сейчас заметила…

x86-
()
Ответ на: комментарий от fsb4000

Кстати, пардон за оффтоп, но какое, по твоему мнению, будет значение согласно стандарту C (и какие именно параграфы об этом говорят):

int i = -1;
printf("%u\n", *(unsigned*)&i);
anonymous
()
Ответ на: комментарий от anonymous

Тебя послушать, так и в юнионе нельзя писать в один член, а читать из другого (когда они достаточно несовместимых типов).

В С++ нельзя, насколько я помню.

В С вроде можно.

А вообще нужно не бояться использовать memcpy, компиляторы обычно оптимизируют эти случаи, и получается такой же код, как и при кастах, но более надёжный так как не развалится со временем…

fsb4000 ★★★★★
()

Кстати да, можно каст выбросить. Получится тогда uint16_t buf = htonh(*(p + i));, что читабельней

x86-
()
Ответ на: комментарий от x86-

Лишнее приведение к указателю на uint16.

Нужно так

  • (p + i) ака (((char*)p) + (sizeof(p) * i)) смещаемся по индексу

  • *(p + i) получаем значение по указателю

  • (uint16_t )*(p + i) кастуем значение к иному типу (ибо знаем что влезет)

То есть так (uint16_t )*(p + i).

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от fsb4000

В С вроде можно.

Как мне ответил один из мемберов WG14, я прав насчёт того, что в стандарте C нет вординга который бы ясно разрешал это.

anonymous
()
Ответ на: комментарий от alysnix

Каст уже при передаче в аргумент функции значения что-бы компилятор не ругался, а мы явно говорим что здаем что делаем мол рот свой гцц ты прикрой.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

А, точно. Теперь работает и c -O2

#include <stdio.h>
#include <inttypes.h>

uint16_t htonh(uint16_t a) {
    return a + 2;
}

int main() {
    uint32_t p[] = { 1,2,3 };
    
    for (size_t i = 0; i < 3; i++) { 
        uint16_t buf = htonh(*(p + i));
        printf("%d\n", buf);
    }

}

Вывод при -О2

3
4
5
x86-
()
Ответ на: комментарий от fsb4000

В С++ нельзя, насколько я помню.

а кто этому может помешать? как компилятор установит, что ты ставил одно поле юниона, а потом, где-то прочитал из другого? ты мог это делать в разных местах программы с разницей в полтора года по времени.

юнион это вообще глубоко системный прибабах, необходимый для случаев когда один и тот же код интерпретирует данную область памяти по разному. например float в виде массива байтов.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от x86-

Ну вот и всё с ТС 50$. Но всё же в таком коде надо себя 10000 раз спросить уверены ли мы в i и уверены ли мы в значениях массива p. А то если чего вдруг все волосы свои повыдёргиваешь пытаясь на другом конце другую программу отладить которой шлются данные с приколом.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от anonymous
int i = -1;
printf("%u\n", *(unsigned*)&i);

Поскольку в C2X ввели two’s complement sign representation , то -1 == UINT_MAX https://en.cppreference.com/w/c/23

int* мы можем приводить к любому указателю, то есть и к unsigned* http://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p7

Разыменовать мы тоже можем, потому что http://port70.net/~nsz/c/c11/n1570.html#6.5p7

* a type that is the signed or unsigned type corresponding to the effective type of the object

Так что я думаю что тут всё норм, и выведется UINT_MAX, то есть 4294967295, для распространенных платформ…

fsb4000 ★★★★★
()
Ответ на: комментарий от anonymous

Ты берёшь адрес переменной и приводишь его к unsigned типу затем разыменовав ты получишь бинарное значение signed типа в unsigned представлении то есть ты получишь UINT_MAX и всё. Это всё у тебя тоже самое что и printf("%u\n", (unsigned)-1); просто записано с излишними телодвижениями в надежде запутать.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Ты берёшь адрес переменной и приводишь его к unsigned типу затем разыменовав ты получишь бинарное значение signed типа в unsigned представлении то есть ты получишь UINT_MAX и всё.

В цепепе это не так и в коде UB. Мне интересно где C гарантирует то, что пишешь ты. В cast unsigned int * to unsigned short * (комментарий)?

anonymous
()
Ответ на: комментарий от anonymous

И не должно быть сказано. У тебя есть 4 байта бинарных данных как угодно кастуй в пределах 4 байтовых типов данные как были такими же и останутся, меняется лишь их интерпретация. Один и от же набор бит в unsigned и signed имеет разное десятичное представление. Но бинарное значение одно и тоже в данном случае. Ты просто делаешь кольцевое переполнение типа и всё, смещая его значение на 1 больше возможного для беззнакового.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от alysnix

а кто этому может помешать?

Никто. И люди делают, поэтому компиляторы подстраиваются…

https://en.cppreference.com/w/cpp/language/union

It’s undefined behavior to read from the member of the union that wasn’t most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

It’s undefined behavior to read from the member of the union that wasn’t most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.

а как они могут помешать читать с другим типом? там никаких признаков типа нет, кроме данных. а если будет признак типа - это уже не юнион. узнать, каким типом писали в юнион компилятор в общем случае не может, кроме тривиального. в рантайме тоже не может - там нет инфы об этом.

alysnix ★★★
()
Ответ на: комментарий от anonymous

Да, си гарантирует что если ты переполнишь тип то получишь его переполненное значение, то есть максимальное. Или минимальное если переполнишь знаковое в отрицательном значении на 1. Тоесть тип будет по кругу переполняться и принимать значения от своего минимального до максимального столько раз сколько ты его переполнишь. Например unsigned char ch можно в цикле гонять 1024 раза при этом он 4 раза пройдёт круг от 0 до 255 сбросится до 0 а потом опять до 255 и так 4 раза.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от anonymous

Да, гарантированно тип значения объекта определяется типом используемым для доступа к нему. То есть вне зависимости от типа значения если ты его кастуешь к другому типу то доступ к значению будет осуществляться исключительно в рамках того типа к котрому ты кастуешь из изначального типа будут браться только бинарные данные в пределах его размера. Тоесть если ты char к long скастовал то ты получаешь работу с long char теперь нет и не было никогда есть только бинарное предствление char в виде типа long.

Проще говоря. В какой тип привёл такой он и будет всё что до, теперь просто данные бинарные.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от anonymous

Потому что в c++ не все типы фундаментальные. Многие это сахар скрывающий рантайм, а не данные. Именно поэтому ты не можешь что угодно скастовать к чему угодно. За переменной у тебя стоит код, а не данные. Это не бардак, а работа с данными и их массивами. Типы нужны просто для разного подхода к доступу и обработке одних и тех же бинарных данных в памяти и всё. А у тебя абстракции данных нужные для разного подхода к обработке данных, а не к их доступу. Не путай тёплое с мягким. В пределах фундаметальных типов которые и в си и в си++ одинаковы у тебя будет всё тоже самое что и в си.

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

Тоесть если ты char к long скастовал то ты получаешь работу с long char теперь нет и не было никогда есть только бинарное предствление char в виде типа long.

https://gcc.gnu.org/bugs/#nonbugs

Casting does not work as expected when optimization is turned on.

    This is often caused by a violation of aliasing rules, which are part of the ISO C standard. These rules say that a program is invalid if you try to access a variable through a pointer of an incompatible type. This is happening in the following example where a short is accessed through a pointer to integer (the code assumes 16-bit shorts and 32-bit ints):

        #include <stdio.h>

        int main()
        {
          short a[2];

          a[0]=0x1111;
          a[1]=0x1111;

          *(int *)a = 0x22222222; /* violation of aliasing rules */

          printf("%x %x\n", a[0], a[1]);
          return 0;
        }

    The aliasing rules were designed to allow compilers more aggressive optimization. Basically, a compiler can assume that all changes to variables happen through pointers or references to variables of a type compatible to the accessed variable. Dereferencing a pointer that violates the aliasing rules results in undefined behavior.

    In the case above, the compiler may assume that no access through an integer pointer can change the array a, consisting of shorts. Thus, printf may be called with the original values of a[0] and a[1]. What really happens is up to the compiler and may change with architecture and optimization level.

    Recent versions of GCC turn on the option -fstrict-aliasing (which allows alias-based optimizations) by default with -O2. And some architectures then really print "1111 1111" as result. Without optimization the executable will generate the "expected" output "2222 2222".

    To disable optimizations based on alias-analysis for faulty legacy code, the option -fno-strict-aliasing can be used as a work-around.
fsb4000 ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

В пределах фундаметальных типов которые и в си и в си++ одинаковы у тебя будет всё тоже самое что и в си.

В пределах фундаментальных типов в C++ тут:

int i = -1;
printf("%u\n", *(unsigned*)&i);

однозначно UB. Так что это не то же самое, что в C.

anonymous
()
Ответ на: комментарий от anonymous

однозначно UB. Так что это не то же самое, что в C.

Нужно писать как можно проще, чтобы приходилось меньше думать над кодом…

И избегать reinterpret_cast :)

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Это всё в рамках оптимизаций. На деле у нас данные из 4х байт в виде массива из двух ячеек по 2 байта. Но запись мы ведём разовую сразу в 4 байта. Понятно что оптимизатор об такое будет спотыкаться. На деле это правильный код , а оптимизации это вообще с краю и никакого отношения к стандартам и вообще языку не имеют.

Конечно будет UB если массив из 1 элемента 2 байтовый, а мы 4 впихиваем после каста. Но суть то в том что тут соблюдается гарантия что к какому типу скастовали с тем и работаем, а от предыдущего остаются лишь данные. Так что тут всё верное. Ну а то что мы можем скастовать в указателю на другой тип это немнодечко не то что мы можем скастовать один тип к другому. Вот если бы мы сначала разыменовали тип, затем привели задали ему значение его переполняющее ну переполнился бы он по кругу и всё.

Но указатель это не тип. И никакого отношения к типам не имеет. Типа данных и указатели на них это вообще абсолютно разные вещи. Выше я писал про фундаментальные типы они могут кастоваться друг в друга как в карусели.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от alysnix

а как они могут помешать читать с другим типом? там никаких признаков типа нет

Ну например так(текущие компиляторы так не делают, пример вымышленный):

union test {
    int a;
    float b;
};

extern bool f;

int bar() {
    test t;
    t.b = 5.0f;
    if (f) {
        return t.a; 
    }
    return 0;
}
// "мысли компилятора":
// мы писали в t.b, значит читать из t.a мы не можем
// иначе UB, а в программе нет UB.
// значит всю функцию можно сократить до
// return 0;
fsb4000 ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Выше я писал про фундаментальные типы они могут кастоваться друг в друга как в карусели.

С которой ты упал в детстве и стукнулся головой так, что теперь несёшь то, что несёшь?

anonymous
()
Ответ на: комментарий от fsb4000

Ну например так(текущие компиляторы так не делают, пример вымышленный):

это тривиальный случай. если компилятор осиливает локальный статический анализ кода, он это увидит.

а если ты вызвал внешнюю фунцию(из другой единицы компиляции или вообще чужой либы), которой передал юнион… то, что там с ним делали - компилятор не знает.

а поскольку с точки зрения теории, тип и локально и нелокально должен проявлять одинаковое поведение, то исправляющий код генериться не должен в локальном случае, а варнинг может быть выдан.

alysnix ★★★
()
Ответ на: комментарий от anonymous

Не бубни я про это

    char ch = 55;
    printf("%i\n",(char)(unsigned long long)(short)(long)(unsigned long)(signed char)(signed long)(unsigned short)ch)

Если значение влазит везде то всегда всё будет хорошо. Если не влезло где то что можно заранее расчитать где не влезет и какое итоговое значение будет по всей цепочке каста.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Это преобразование значений, а не переинтерпретация объекта.

anonymous
()
Ответ на: комментарий от alysnix

а как они могут помешать читать с другим типом?

Да оптимизатор такое в два счёта поломает:

//1.cpp
void calc(short *s, int *i) {
   for (int cnt = 0;  cnt < 5;  ++ cnt)
      *i += *s;

//2.cpp
union U {
   short s;
   int i;
};
void main() {
   U u{.i=5};
   calc(&u.s, &u.i);
}

Наверняка с оптимизацией получишь другой результат нежели без.

kvpfs ★★
()

Вообще удивительно, как много людей здесь за кривые касты выступает. Ну может вы свой код не оптимизируете … Любой reinterpret_cast не в char* нужно смотреть под лупой и иметь для этого веские основания.

kvpfs ★★
()

По поводу кастов у меня тоже один вопрос имеется. Сейчас хочу заюзать примерно такое:

template <typename F>
class D;

template <typename F>
class B {
protected:
	B(F cb) : m_cb(cb) {}
	F m_cb;
	void fn() {m_cb(reinterpret_cast<D<F>*>(this));}
};

template <typename F>
class D : B<F> {
public:
	D(F cb) : B<F>(cb) {}
	void fn() {B<F>::fn();}
	/*any data*/
};

int main() {
	D d{[](auto *p){}};
	d.fn();
}

Видите ли вы в этом reinterpret_cast’е какую-нибудь проблему (лично я нет, вроде норм, но всё равно сыкотно как-то)? Если ок, то как бы воткнуть такой static_assert для проверки того, что B и D совместимы (например, если D - наследник от нескольких базовых классов через множественное наследование, то так очевидно нельзя)?

Почему так получилось и такой костыль хочу? D - это красивый интерфейсный класс, который показывает юзеру нужное и закрывает остальное, все вызовы переправляет в B, при вызове колбэка из B хочу передавать этот же красивый интерфейс, а не кишки с полным доступом во всё (B).

kvpfs ★★
()
Ответ на: комментарий от fsb4000

Спасибо, так и сделаю. Почему-то был уверен, что static_cast здесь не вкрутить, сейчас понимаю, что зависимый тип и всё такое, но ощущение было такое.

kvpfs ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.