адресная арифметика и выравнивание

sergey_m

Есть две структуры
struct a_s;
struct b_s;
о выравнивании которых ничего не известно. Возможно оно есть, возможно его нет. Хочется их аллоцировать и хранить вместе, одну за другой. Есть следующий код:


struct a_s *a;
a = (struct a_s *)malloc(sizeof(struct a_s) + sizeof(struct b_s
.......
b = (struct b_s *a + 1);


Вопрос: будет ли теперь b указывать на начало struct b_s на любых архитектурах при любом выравнивании?

Hastya

Так, типа, выравнивание разве распространяется на динамическую память?

shlyumper

чисто теоретически должен. а зачем такой изврат?
такое компиляется, по крайней мере:


struct a_s {
int q;
};
struct b_s {
char z;
};
#define max(a,b) (a>b?a:b)
struct padding {
char padding[max(sizeof(struct a_s sizeof(struct b_s];
};
#undef max
struct padding* array;
...

Dasar



void * buf = malloc(sizeof(struct a_s) + sizeof(struct b_s;
struct a_s *a;
a = (struct a_s *)buf
.......
b = (struct b_s *buf + sizeof(struct a_s;


Так будет надежнее.

shlyumper

Надежнее, но некрасиво. Тут еще вариант в голову пришел:


union padding {
struct a_s a;
struct b_s b;
};
union padding* array;
...
a = (struct a_s*array + 12);
b = (struct b_s*array + 21);

sergey_m

Лев, маза в том, что я предпочитаю так:


struct c_s {
struct a_s a;
struct b_s b;
};
struct c_s *c;
c = (struct c_s *)malloc(sizeof(struct c_s;
a = &c->a;
b = &c->b;


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

Dasar

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

Dasar

Основных премуществ два:
1. Лучше читается
2. Меньше вероятность ошибки.
Минусы следующие:
1. если величина выравнивания стоит довольно большая, то будут соответсвующие потери
2. При разных величинах выравнивания будут получаться разные варианты совместного размещения структур, что приведет к проблемам, если совместный блок сохранется на винт, передается по сети.

sergey_m

1. если величина выравнивания стоит довольно большая, то будут соответсвующие потери
Аллоцируется не в таких уж и больших количествах. Скорость важнее расходов памяти.
При разных величинах выравнивания будут получаться разные варианты совместного размещения структур, что приведет к проблемам, если совместный блок сохранется на винт, передается по сети.
Ни того не другого не происходит.
Есть еще недостаток из-за которого спор и возник: struct c_s {} объявляется в инклюднике включаемом большим числом программ, а struct a_s {} в другом, не имеющем к большинству из этих программ никакого отношения. Получается, что для того что бы скомпилиться им нужно включить второй инклюдник тоже.

Arhim

void *, malloc - это зло. Только new. О конструкторе-то кто заботиться будет?
А собственно зачем все это надо, может компилятор лучше знает как надо?

sergey_m

Речь идет о языке Це, если кто-то не догадался.

Arhim

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

Chupa

> b = (struct b_s *a + 1);


$ cat a.c
#include <stdio.h>
struct a{
char fa[3];
} sa;
struct b{
int fb;
} sb;
struct c{
struct a fa;
struct b fb;
} sc;
int main
{
printf ("a=%d b=%d c=%d (c.b=%d)\n",
sizeof(sa
sizeof(sb
sizeof(sc
char*&sc.fb-char*&sc;
}
$ gcc a.c
$ ./a.out
a=3 b=4 c=8 (c.b=4)
$


Маза, если расположить структурки a и b в памяти друг за другом,
то обращение к int в struct b будет по невыровненному адресу.
Не каждая архитектура такое позволит.

sergey_m

Правила раздела ты уже прочел?

sergey_m

Маза, если расположить структурки a и b в памяти друг за другом,
то обращение к int в struct b будет по невыровненному адресу.
На i386 все в порядке. Так как тип a правильный, то (a + 1) увеличивает указатель с учетом выранивания, то есть на 4 в твоем примере. Поэтому меня интересует на каких архитектурах это будет не так? И будет ли?

Chupa

> Так как тип a правильный, то (a + 1) увеличивает указатель
> с учетом выранивания, то есть на 4 в твоем примере.
не на 4, а на 3=sizeof(struct a)
можешь проверить

sergey_m

Как раз проверил, пока ждал твоего ответа.


struct a{
char fa[3];
};
struct b{
int fb;
};
int main
{
struct a *sa;
struct b *sb;
sa = (struct a *)malloc(sizeof(struct a)+sizeof(struct b;
sb = (struct b *sa + 1);
printf ("a=%d b=%d (&fa - &fb=%d)\n",
sizeof(sa
sizeof(sb
char*&sb->fb - char*&sa->fa;
}


a=4 b=4 (&fa - &fb=3)

sergey_m

Вот это еще хорошо иллюстрирует ошибку:


printf ("*sa = %u, *sb = %u\n", (unsigned int) sa, (unsigned int) sb);

sergey_m

Спасибо!

Marinavo_0507



printf ("a=%d b=%d (&fa - &fb=%d)\n",
sizeof(sa
sizeof(sb
char*&sb->fb - char*&sa->fa;


sa, и sb - это указатели, и sizeof от них == 4
надо писать sizeof(*sa)

sergey_m

Мда. В точку попал. Ошибки нету.


printf ("a=%d; b=%d; (&fa-&fb)=%d\n",
sizeof(struct a
sizeof(struct b
char*&sb->fb - char*&sa->fa;


a=3; b=4; (&fa-&fb)=3
*sa = 134524976, *sb = 134524979

Chupa

> Ошибки нету.
Ошибка в сложении ищется? Её там не будет.
> *sb = 134524979
А вот корявое выравнивание -- запросто.

Dasar

> Не каждая архитектура такое позволит.
Такие архитектуры и вправду встречаются?
Мне казалось, что к невыровновнем данным обращаться можно всегда, но такое обращение будет тормозить, т.к. требует больше циклов шины.

sergey_m

В ARM кажется нельзя вообще.

Chupa

>> Не каждая архитектура такое позволит.
> Такие архитектуры и вправду встречаются?
Если речь именно об архитектуре, то встречаются.
Пример, скоторым я недавно столкнулся -- на оптероне нужно выравнивать
стек на границу 16 байт, иначе вызов sse'шной инструкции, которая предполагает
такой alignment (movaps внутри вызова printf для печати double, например валит прогу
с SEGV. Тут, правда, это было на моей совести, т.к. стек я делал руками.
В общем случае это на совести компилера, который подобную инструкцию
позовёт только тогда, когда уверен, что косяков не будет, или на совести
писателей оптимизированных под архитектуру асмовых макросов.
> Мне казалось, что к невыровновнем данным обращаться можно всегда,
> но такое обращение будет тормозить, т.к. требует больше циклов шины.
В лучшем случае -- больше циклов, в худшем -- обработчик исключительной
ситуации для эмуляции кривой инструкции.
Возможности проверить что именно происходит нету,
но доступ к невыровненным данным проходит успешно на оптероне
(alignment check похоже вылючен) и ppc.
Можно попытаться замерить падение производительности.

sergey_m

Ошибка в сложении ищется? Её там не будет.
Ищутся возможные последствия такой адресной арифметики.

Marinavo_0507

> в худшем -- обработчик исключительной
> ситуации для эмуляции кривой инструкции.
Если есть такой обработчик.
Внутри ядра к примеру -- скорее всего нет.

rosali


void * buf ...
buf + sizeof(struct a_s)

На С++ void * с int-ом нельзя сложить, только на чистом C.

maggi14

MSVC это делает без особых проблем в Си++ -проектах

rosali

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

Так вроде же у выравнивания, а не у его отсутствия есть достоинства, иначе зачем бы его делали?

rosali

Такие архитектуры и вправду встречаются?

Palm

rosali

MSVC это делает

Может быть...


xenon:~$ cat > a.c
int main(int argc, char * argv[])
{
void * x = 0;
void * y = x + 1;
}
xenon:~$ g++ a.c
a.c: In function `int main(int, char**)':
a.c:4: error: pointer of type `void *' used in arithmetic
xenon:~$

rosali

Можно попытаться замерить падение производительности.

На низкий alignment кстати офигенно реагирует механизм когерентности кешей на SMP. До 10 раз можно встрять!

maggi14

Да, пардон, я соврал. int* - все работает, void* - не может определить размер.
Приношу свои извинения за дезу

rosali

На С++ следует писать char *. А, кстати, есть уверенность, что на всех архитектурах
sizeof(char) == 1?

maggi14

По-моему, по стандарту - да. Но далеко не все компиляторы и не все платформы живут по стандартам.

smnikiforov

для любых чаров (и только для них) стандарт гарантирует размер 1(пункт 5.3.3 стандарта-98)

DiDiPi

Такие архитектуры и вправду встречаются?
Palm
Ты путаешь ОС с процессорной архитектурой ARM, которая используется в некоторых (современных) устройствах на этой ОС.

rosali

иначе вызов sse'шной инструкции, которая предполагает
такой alignment (movaps внутри вызова printf для печати double, например валит прогу
с SEGV

А ты уверен, что невыровненный movaps приводит именно к SEGV? Скорее уж какой-нибудь SEGILL или еще чего... У меня под виндой вообще в Privileged instruction свалился В доступе под unix-ом у меня только Athlon есть, там вообще SSE нету , но из общих соображений -- что movaps-у в printf-е делать? Может ты просто в чем другом напутал?
стек я делал руками

Chupa

это ты в чём-то напутал
movaps - move aligned packed single-precision floating poing
privilege level ей нужен 3 (то есть любой)
при нарушении выравнивания вызывает general protection exception
короче, иди читай доки до просветления
http://www.amd.com/us-en/assets/content_type/DownloadableAssets/dwamd_26568.pdf

rosali

Че мне доки читать, я взял и запустил, мне отладчик окошко выдал... Ладно, во вторник пойду на работу, запущу под Linux-ом, расскажу...
Оставить комментарий
Имя или ник:
Комментарий: