Перейти к содержанию
    

Указатель на невыровненную память

CM4

		operator T () const {return v;}
 800044a:	f8b2 3001 	ldrh.w	r3, [r2, #1]
	v = *vPtr + vPtr[0];
 800044e:	005b      	lsls	r3, r3, #1
 8000450:	b29b      	uxth	r3, r3
 8000452:	800b      	strh	r3, [r1, #0]
		Packed &operator = (const T &value) {v = value; return *this;}
 8000454:	230a      	movs	r3, #10
 8000456:	7053      	strb	r3, [r2, #1]
 8000458:	2300      	movs	r3, #0
 800045a:	7093      	strb	r3, [r2, #2]

Чтение 2 байта, а запись по одному

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Можно избавиться от приведений типа:

typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
typedef unsigned long long ulonglong;
typedef signed long long longlong;
typedef signed char s8;
typedef signed short s16;
typedef signed long s32;
typedef signed long long s64;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
typedef unsigned long long u64;


#pragma pack(push, 1)
struct PackU64 {
  u64 d64;
};
#pragma pack(pop)
struct Qqq {
  u8 x1;
  PackU64 x2;
};
Qqq volatile qqq;

u32 volatile z;

void F1(PackU64 volatile *p)
{
  u64 q = p->d64;
  z = q;
  z = q >> 32;
}

int main()
{
  z = qqq.x1;
  F1(&qqq.x2);
  return 0;
}

Результат аналогичный.

11 минут назад, Ivan. сказал:

CM4

		operator T () const {return v;}
 800044a:	f8b2 3001 	ldrh.w	r3, [r2, #1]
	v = *vPtr + vPtr[0];
 800044e:	005b      	lsls	r3, r3, #1
 8000450:	b29b      	uxth	r3, r3
 8000452:	800b      	strh	r3, [r1, #0]
		Packed &operator = (const T &value) {v = value; return *this;}
 8000454:	230a      	movs	r3, #10
 8000456:	7053      	strb	r3, [r2, #1]
 8000458:	2300      	movs	r3, #0
 800045a:	7093      	strb	r3, [r2, #2]

Чтение 2 байта, а запись по одному

Только команд как-то слишком многовато... Получилось более громоздко, чем если бы побайтно прочитал и потом склеил OR-ом. А значит - бессмысленно.  :unknw:

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

PS: Для корректного теста используйте 64-битную переменную, а не 16-битную. Ваш тест - некорректный.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 часа назад, jcxz сказал:

Можно избавиться от приведений типа:

Результат аналогичный.

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

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

Т.е. как раз то, что он указывал в начальном топике

Цитата
uint16_t *vPtr __attribute__((packed)) = &foo.v2;
*vPtr = 10;


И, насколько я понял, в GCC нет специального ключевого слова для такой пометки: у IAR это (как я понял) - __packed, у CLang - __unaligned, а в GCC - нету. Вернее, есть вот такое "решение" в лоб

struct __attribute__((packed)) T_UINT32 { uint32_t v; };

#define __UNALIGNED_UINT32(x) (((struct T_UINT32 *)(x))->v)

но возможные проблемы из-за нагрушения strict aliasing, я так полагаю, программисту предлагают разруливать самому.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Arlleex сказал:

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

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

ТС изначально (в изначальном посте) писал про передачу не указателя на член структуры, а указателя на переменную, являющуюся членом упакованной структуры (являющуюся невыровненной переменной).

В 18.10.2023 в 14:19, Ivan. сказал:

Теперь попробуем взять указатель на данное поле:

uint16_t *vPtr = &foo.v2;

Компилятор выдаст предупреждение: warning: taking address of packed member of 'Foo' may result in an unaligned pointer value (и это тоже правильно)

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

Указатель на член структуры в терминологии си - это всё-таки нечто совершенно другое. Не путайте.

 

И как раз для этого я и привёл пример: Передаю указатель на переменную, являющуяся членом упакованной структуры: &qqq.x2. Т.е. - невыровненную переменную.

1 час назад, Arlleex сказал:

И, насколько я понял, в GCC нет специального ключевого слова для такой пометки: у IAR это (как я понял) - __packed, у CLang - __unaligned, а в GCC - нету. Вернее, есть вот такое "решение" в лоб

Как нет??? Я же привёл решение. И по листингу видно, что GCC его воспринимает так как и нужно. И даже в комменте пишет "@ unaligned". Вы листинги смотрели?

Код гененится примерно такой же, как у IAR с __packed. Т.е. - это полный аналог. Только более громоздкий (в исходнике).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

26 минут назад, jcxz сказал:

Указатель на член структуры в терминологии си - это всё-таки нечто совершенно другое. Не путайте.

В чистом Си есть указатель на член структуры?:wacko2: Что-то новенькое. В С++ - знаю такой, однако из контекста вполне понятно, о чем я говорил, написав "указатель на член структуры". Даже цитату привел из первого топика с кодом.

 

26 минут назад, jcxz сказал:

И как раз для этого я и привёл пример: Передаю указатель на переменную, являющуяся членом упакованной структуры: &qqq.x2

Вы передали указатель на данные типа упакованной структуры:

void F1(PackU64 volatile *p)

'qqq' - это не упакованная структура, соответственно, x2 - это не член упакованной структуры. Это член неупакованной структуры 'qqq', который имеет тип упакованной структуры PackU64. Это не то, что хочет ТС. ТС хочет получить &qqq.x2.d64, и присвоить этот адрес указателю на u64. А сделать это напрямую - нельзя.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

54 минуты назад, Arlleex сказал:

Вы передали указатель на данные типа упакованной структуры:

void F1(PackU64 volatile *p)

'qqq' - это не упакованная структура, соответственно, x2 - это не член упакованной структуры. Это член неупакованной структуры 'qqq', который имеет тип упакованной структуры PackU64.

Все члены Qqq имеют выравнивание ==1, а значит Qqq можно считать упакованной.

54 минуты назад, Arlleex сказал:

Это не то, что хочет ТС. ТС хочет получить &qqq.x2.d64, и присвоить этот адрес указателю на u64. А сделать это напрямую - нельзя.

Вообще-то, если прочитать исходный пост, то видно, что ТС хочет передавать указатель на невыровненную переменную (см. название топика) без варнингов. Возникает такая потребность при взятии указателя на член упакованной структуры. Т.е. - ему важна не сама формальная упакованность структуры, а возможность корректной передачи указателей на её члены. Если вы заметите - то он передаёт указатель как указатель на обычный тип даных (не имеющий никакого отношения к структурам). А значит моё решение: Описать структуру как я предложил и передавать указатели на её члены - вполне подходящее.

ТСу очевидно нужно решить какую-то конкретную практическую задачу корректно (например описать структурой кадр какого-то протокола). И передавать указатели на его члены. А какая при этом будет структура (по формальным признакам) - упакованная или нет - это дело десятое. Главное, чтобы компилятор правильно с ней работал. Правильно и оптимально (где возможно - многоразрядными инструкциями, где нет - побайтово).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

22 минуты назад, jcxz сказал:

Все члены Qqq имеют выравнивание ==1, а значит Qqq можно считать упакованной.

Это еще почему? Qqq как раз не имеет выравнивания == 1, поэтому как раз ее члены - не упакованные, если это не задано явным образом для ее членов. Но тут и вопрос формальности: конкретно щас без дополнительных элементов в Qqq она, конечно, упакованная. Но это мы формально понимаем по текущему ее наполнению в виде x1 и x2. А компилятор вправе считать не так, т.к. никаких атрибутов упаковки для самой Qqq нет. Другими словами., добавьте в Qqq еще элемент int x3 и перед ним будет заполнение в 3 байта.

Я лишь трактовал топик ТС как написано: сообщить компилятору при взятии адреса переменной-члена структуры, что этот адрес может быть невыровненным (из-за упаковки этой структуры). Т.е сказать компилятору, что при работе с указателем вида

uint16_t __unaligned *p = &Str.Member;

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

7 часов назад, jcxz сказал:

Можно избавиться от приведений типа:

typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
typedef unsigned long long ulonglong;
typedef signed long long longlong;
typedef signed char s8;
typedef signed short s16;
typedef signed long s32;
typedef signed long long s64;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
typedef unsigned long long u64;


#pragma pack(push, 1)
struct PackU64 {
  u64 d64;
};
#pragma pack(pop)
struct Qqq {
  u8 x1;
  PackU64 x2;
};
Qqq volatile qqq;

u32 volatile z;

void F1(PackU64 volatile *p)
{
  u64 q = p->d64;
  z = q;
  z = q >> 32;
}

int main()
{
  z = qqq.x1;
  F1(&qqq.x2);
  return 0;
}

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


Вставил как есть сейчас этот тест в Keil (LLVM ARM Compiler 6): получил Fault, т.к. сгенерировалась асм-команда LDRD.

Вообще, стандарт Си говорит, что если указатель на результирующий тип не выровнен на естественную границу этого типа, поведение не определено.

И это даже логично: указатель помечен как volatile. В чью пользу компилятор должен отработать: правильно работать с volatile-объектом (не читать его составные части по-отдельности) - а это возможно только при корректно выровненном указателе; или же забить на volatile-ность и читать объект по байтам/кускам, удовлетворяя предположению о невыровненном указателе? Это два противоречащих друг другу требования.

Даже банальный пример в виде простого обращения к qqq.x2.d64 на обыкновенное чтение в main() так же генерирует LDRD и, соответственно, Fault.

Такие дела:smile:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

15 часов назад, jcxz сказал:

Только команд как-то слишком многовато... Получилось более громоздко, чем если бы побайтно прочитал и потом склеил OR-ом. А значит - бессмысленно.  :unknw:

И что же тут лишнего?

		operator T () const {return v;}
 800044a:	f8b2 3001 	ldrh.w	r3, [r2, #1] ;чтение 2 байт
	v = *vPtr + vPtr[0];
 800044e:	005b      	lsls	r3, r3, #1   ;умножение на 2 вместо повторного чтения
 8000450:	b29b      	uxth	r3, r3       ;обрезание старших байт (возможно бессмыслено, но компилятор всегда так делает не зависимо от реализиции)
 8000452:	800b      	strh	r3, [r1, #0] ;сохранение в глобальную переменную
 
 
 		Packed &operator = (const T &value) {v = value; return *this;}
 8000454:	230a      	movs	r3, #10      ;формирование первого байта
 8000456:	7053      	strb	r3, [r2, #1] ;сохранение байта
 8000458:	2300      	movs	r3, #0       ;формирование второго байта
 800045a:	7093      	strb	r3, [r2, #2] ;сохранение байта

 

15 часов назад, jcxz сказал:

PS: Для корректного теста используйте 64-битную переменную, а не 16-битную. Ваш тест - некорректный.

		operator T () const {return v;}
 8000448:	f8d1 3001 	ldr.w	r3, [r1, #1]
 800044c:	f8d1 2005 	ldr.w	r2, [r1, #5]
	auto vPtr = unaligned(&foo.v2);
	v = *vPtr + vPtr[0];
 8000450:	18db      	adds	r3, r3, r3
 8000452:	4152      	adcs	r2, r2
 8000454:	461c      	mov	r4, r3          ;Не понятно зачем он копирует во временные регистры
 8000456:	4615      	mov	r5, r2          ;чтобы потом сохранить. Но тут пути компилятора неисповедимы
 8000458:	4b08      	ldr	r3, [pc, #32]	; (800047c <main+0x38>)
		Packed &operator = (const T &value) {v = value; return *this;}
 800045a:	2200      	movs	r2, #0
 800045c:	200a      	movs	r0, #10
 800045e:	7048      	strb	r0, [r1, #1]
 8000460:	708a      	strb	r2, [r1, #2]
 8000462:	70ca      	strb	r2, [r1, #3]
 8000464:	710a      	strb	r2, [r1, #4]
 8000466:	714a      	strb	r2, [r1, #5]
 8000468:	718a      	strb	r2, [r1, #6]
 800046a:	71ca      	strb	r2, [r1, #7]
 800046c:	720a      	strb	r2, [r1, #8]
	v = *vPtr + vPtr[0];
 800046e:	e9c3 4500 	strd	r4, r5, [r3] ;сохранение в глобальную переменную результата сложения *vPtr + vPtr[0];

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 10/19/2023 at 12:39 PM, _pv said:

точно так же заворачивать memcpy в макросы 🙂

это правильный ответ 🙂

https://godbolt.org/z/EYn3nb71G

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

23 hours ago, jcxz said:

Ваше предложение?

Они уже туда запихнуты RFC. Как предлагаете это решать?

в каком-то tcp стэке то ли от TI то ли микрочиповском вполне обходились макросами для доставания/запихивания невыровненных данных по определённым смещениям, им видимо проще было так, чем объяснять различными компиляторозависимыми прагмами как данные упаковывать/выравнивать.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 10/19/2023 at 10:01 AM, dimka76 said:

А как быть с протоколами Ethernet и всем тем, что работает поверх него ?

А с Modbus ?

с modbas очень аккуратно один, два, четыре регистра объединением по одному параметру . никаких глобальных объединений массива регистров со структурами!!!!!! иначе теоретических разбирательств до .. и больше.. Про героическое решение проблем было очень правильно сказано.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

19 часов назад, Arlleex сказал:

Это еще почему? Qqq как раз не имеет выравнивания == 1, поэтому как раз ее члены - не упакованные, если это не задано явным образом для ее членов.

А по какому смещению находится член x2? Посмотрите в листинг.

19 часов назад, Arlleex сказал:

Другими словами., добавьте в Qqq еще элемент int x3 и перед ним будет заполнение в 3 байта.

Естественно - добавлять в неё можно только элементы, оформленные подобно x2. Для чего следует написать все нужные структуры PackUxx

19 часов назад, Arlleex сказал:

Я лишь трактовал топик ТС как написано: сообщить компилятору при взятии адреса переменной-члена структуры, что этот адрес может быть невыровненным

Так и есть.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

18 часов назад, Arlleex сказал:

Вставил как есть сейчас этот тест в Keil (LLVM ARM Compiler 6): получил Fault, т.к. сгенерировалась асм-команда LDRD.

В топку такой компилятор. :unknw:  Не понимающий, что на Cortex-M нельзя использовать LDRD для невыровненных адресов.

Пробовал скормить свой пример GCC v9.2.1 и GCC v12.2.0 - оба создали правильный код.

18 часов назад, Arlleex сказал:

Вообще, стандарт Си говорит, что если указатель на результирующий тип не выровнен на естественную границу этого типа, поведение не определено.

Ссыль?

18 часов назад, Arlleex сказал:

И это даже логично: указатель помечен как volatile. В чью пользу компилятор должен отработать: правильно работать с volatile-объектом (не читать его составные части по-отдельности) - а это возможно только при корректно выровненном указателе; или же забить на volatile-ность и читать объект по байтам/кускам, удовлетворяя предположению о невыровненном указателе? Это два противоречащих друг другу требования.

В чём именно противоречие и причём тут volatile?

volatile вроде только запрещает оптимизацию обращений к объекту. Запрещает создание его копий (в регистрах или ещё где). Но не должен накладывать никаких ограничений на чтение/запись по частям. Это если следовать даже элементарной логике. Иначе - как тогда работать с невыровненными volatile-объектами на ARM7/ARM9 например? или других CPU, не поддерживающих невыровненный доступ аппаратно.

Вы мешаете в кучу volatile и невыровненность. Это совершенно параллельные друг другу субстанции.

В том своём примере volatile я добавил только чтобы компилятор не выкинул код при оптимизации. В реале он там не нужен. Думал - это должно быть понятно априори.  :wink:

18 часов назад, Arlleex сказал:

Даже банальный пример в виде простого обращения к qqq.x2.d64 на обыкновенное чтение в main() так же генерирует LDRD и, соответственно, Fault.

Может Вы что-то с ключами компиляции намудрили? Ну или иначе - баг в компиляторе.  :unknw: ...и нужно писать в поддержку. Я Кейлом не пользуюсь.

9 часов назад, Ivan. сказал:
		operator T () const {return v;}
 8000448:	f8d1 3001 	ldr.w	r3, [r1, #1]
 800044c:	f8d1 2005 	ldr.w	r2, [r1, #5]
	auto vPtr = unaligned(&foo.v2);
	v = *vPtr + vPtr[0];
 8000450:	18db      	adds	r3, r3, r3
 8000452:	4152      	adcs	r2, r2
 8000454:	461c      	mov	r4, r3          ;Не понятно зачем он копирует во временные регистры
 8000456:	4615      	mov	r5, r2          ;чтобы потом сохранить. Но тут пути компилятора неисповедимы
 8000458:	4b08      	ldr	r3, [pc, #32]	; (800047c <main+0x38>)

Ну да - здесь тоже всё правильно. Но требует си++.

3 часа назад, _pv сказал:

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

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

И получается: умность_компиляторов * умелость_программеров == const  :wink:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, jcxz сказал:

Естественно - добавлять в неё можно только элементы, оформленные подобно x2. Для чего следует написать все нужные структуры PackUxx

Я Вашу мысль понял, просто (ИМХО) автор хотел без промежуточных структур.
 

Цитата

Ссыль?

ISO/IEC 9899:201x

Цитата

6.3.2.3 Pointers

7 A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned68) for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.

 

Цитата

В чём именно противоречие и причём тут volatile?

volatile вроде только запрещает оптимизацию обращений к объекту. Запрещает создание его копий (в регистрах или ещё где). Но не должен накладывать никаких ограничений на чтение/запись по частям. Это если следовать даже элементарной логике. Иначе - как тогда работать с невыровненными volatile-объектами на ARM7/ARM9 например? или других CPU, не поддерживающих невыровненный доступ аппаратно.

На свой страх и риск - можно, и ARM, например, у себя и пишет, ARMv7-M Architecture Reference Manual

Цитата

A3.5.7 Memory access restrictions

An instruction that generates an unaligned memory access to Device or Strongly-ordered memory is UNPREDICTABLE.

For instructions that generate accesses to Device or Strongly-ordered memory, implementations must not change the sequence of accesses specified by the pseudocode of the instruction. This includes not changing:
— How many accesses there are.
— The time order of the accesses at any particular memory-mapped peripheral.
— The data sizes and other properties of each access.

С точки зрения компилятора ему не известно, что в процессоре есть такое Device, и что есть Strongly-ordered память.

С точки зрения языка ключевое слово volatile описано весьма расплывчато и многое отдано на реализацию

Цитата

6.7.3 Type qualifiers

7 An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously.134) What constitutes an access to an object that has volatile-qualified type is implementation-defined.

 

Цитата

Вы мешаете в кучу volatile и невыровненность. Это совершенно параллельные друг другу субстанции.

Нет, не мешаю, и не мешал. Я указал, что компилятор, видя volatile у объекта, мог сделать предположение, что адрес объекта уже гарантированно выровнен на нужную границу для осуществления корректного доступа, не производящего (возможные) множественные side-effects между двумя точками следования. Если писать volatile-объект по частям (по байтам), то каждая инструкция записи порождает side-effect, что стандартом недопустимо. С другой стороны, если допустить, что компилятор будет соблюдать только "глобальные" характеристики volatile-доступа, а именно, гарантию и упорядоченность доступа относительно других volatile-доступов, то как прикажете ему действовать, если адрес не выровнен и инструкций доступа по неровным адресам нет в данном CPU? Поэтому - судя по листингу GCC - он несколько "забивает" на правила работы с volatile-объектом, зато генерирует корректные инструкции. CLang же, в свою очередь, наоборот - старается соблюдать семантику volatile-доступа, а для этого не остается ничего иного, как заочно наложить строгое требование к выравниванию объекта. И зато стандарту это ближе соответствует.
 

Цитата

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

Вот именно, чтобы не выкинул. Я когда убираю volatile, CLang тоже начинает копировать пословно (LDR/STR) для Cortex-M4, и по-байтно для Cortex-M0. Налицо разные подходы к volatile-доступам у GCC и CLang.
 

Цитата

В топку такой компилятор. Не понимающий, что на Cortex-M нельзя использовать LDRD для невыровненных адресов.

Нет, он все понимает, просто исходит из предположения, что кодер в здравом уме и понимает, что невозможно и рыбку съесть, и кости - вон:smile:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...