Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Компилятор Gcc или так и должнобыть?
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > Cредства разработки для МК > GNU/OpenSource средства разработки
Олег.
Столкнулся с тем что GCC упорно не хочет выполнять следующее, вроде с синтаксисом нет никакого криминала комерческие компиляторы отрабатывают без проблем (Borlad c++ и пр.) не выдавая никаких ошибок и предупреждений

unsigned long int a[] = {1, 2, 3, 4, 5};
unsigned long int b[5];

void* ptr = a;

b[0] = *((unsigned long int*)ptr)++;
b[1] = *((unsigned long long int*)ptr)++;

В обоих случаях отказывается делать инкремент указателя выдавая ошибку хотя здесь явно указатель приводится к указателю на заданный тип, пробовал указатель описать как указатель на тип char или int ситуация не меняется. С точки зрения синтаксиса вроде всё правильно .. пытался искать в стандарте Ansi C но нашел ничего .. Мож у кого есть какие мысли .. не исключаю что может и я что то делаю не коррекктно.. Конечно эту ситуацию можно обойти... Спасибо.
forever failure
Видимо имеется ввиду это:
warning: use of cast expressions as lvalues is deprecated
Означает, что операция преобразования типов не возвращает ссылку на объект, а следовательно не будет выполнятся его модификация.
777777
Цитата(Олег. @ May 2 2008, 08:17) *
Столкнулся с тем что GCC упорно не хочет выполнять следующее, вроде с синтаксисом нет никакого криминала комерческие компиляторы отрабатывают без проблем (Borlad c++ и пр.) не выдавая никаких ошибок и предупреждений

Борланд - худший из всех имеющихся в природе компиляторов, и ссылаться на него как на образец по меньшей мере смешно.
Цитата(Олег. @ May 2 2008, 08:17) *
unsigned long int a[] = {1, 2, 3, 4, 5};
unsigned long int b[5];

void* ptr = a;

b[0] = *((unsigned long int*)ptr)++;
b[1] = *((unsigned long long int*)ptr)++;

Ты пишешь на С, а думаешь по-ассемблерному. Не должно быть в С-программе такого количества преобразований типов. Не пытайся "помочь" компилятору подобным способом - этим ты ему только мешаешь. Просто пиши программу, думая лишь об алгоритме, а об оптимизации компилятор позаботится сам.
umup
попробуйте так :

Код
  b[0] = *((unsigned long int *)ptr++);
  b[1] = *((unsigned long long int*)ptr++);
zltigo
Цитата(777777 @ May 2 2008, 07:46) *
Борланд - худший из всех имеющихся в природе компиляторов...

smile.gif Не худший, но кривейший это точно - борлондячие продукты, что Cишные, что Паскалевские имеют свое "видение мира" и позволяют без труда писать всякую фигню, что, однако, позволило подсадить на них массу новичков считающих, что все так и должно быть sad.gif
Цитата(umup @ May 2 2008, 08:48) *
попробуйте так :

А вот этого не надо, если в оригинальном варианте некоторые компиляторы формально что-могут сделать и оттделаться Warnigs, то тут уже явная ошибка.

Цитата(Олег. @ May 2 2008, 06:17) *
комерческие компиляторы отрабатывают без проблем (Borlad c++ и пр.)

Про "и пр." это Вы для красного словца вверули.
ReAl
Цитата(Олег. @ May 2 2008, 07:17) *
вроде с синтаксисом нет никакого криминала комерческие компиляторы отрабатывают без проблем (Borlad c++ и пр.)

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

По поводу криминала - приведу выдержку из закона в подтверждение того, что выше уже было сказано:
Цитата
3.3.4 Cast operators
...
Preceding an expression by a parenthesized type name converts the
value of the expression to the named type. This construction is
called a cast. /36/
...
36. A cast does not yield an lvalue.
Причём это из стандарта С аж 89 года, в 99 это было подтверждено.
А раз кастированное выражение не lvalue - значит ему нельзя присвоить значение и ++ не проходит.

А обойти можно так:
Код
void* ptr;

char c;
short s;
int i;
long l;

#define GET_VALUE(p,t)  ( *(t*)( (*(char**)&p += sizeof(t)) - sizeof(t) ) )

void foo(void) {
    c = GET_VALUE( ptr, char);
    s = GET_VALUE( ptr, short);
    i = GET_VALUE( ptr, int);
    l = GET_VALUE( ptr, long);
}

При этом, правда, организуется доступ к объекту одного типа через приведение указателя на него к указателю на другой тип, что не всегда безопасно.
Будет предупреждение:
Цитата
warning: dereferencing type-punned pointer will break strict-aliasing rules
umup
Цитата
А вот этого не надо, если в оригинальном варианте некоторые компиляторы формально что-могут сделать и оттделаться Warnigs, то тут уже явная ошибка.

какая ?
компилируется нормально, без предупреждений.

то есть если нужно инкрементировать указатель, приведенный к другому типу :
Код
b[0] = *((unsigned long int *)ptr++);


а если содержимое этого указателя, то нужно добавить внешние скобки :
Код
b[0] = (*((unsigned long int *)ptr))++;


в обоих случаях нет никаких предупреждений.
ReAl
Цитата(umup @ May 2 2008, 09:48) *
попробуйте так :
Код
  b[0] = *((unsigned long int *)ptr++);
  b[1] = *((unsigned long long int*)ptr++);

А это абсолютно другой код. Согласно приоритетов ++ выполнится раньше приведения типа указателя.
А вот инкремент указателя на void - это, кажется, "неопределённое поведение".
zltigo
Цитата(umup @ May 2 2008, 10:23) *
какая ?

Что-то типа, что c операциями с указателеми на void придется обломиться.
Цитата
компилируется нормально, без предупреждений.

Имя компилятора в студию smile.gif


Цитата(ReAl @ May 2 2008, 10:33) *
А вот инкремент указателя на void - это, кажется, "неопределённое поведение".

Без "кажется" - компилятор который молча инкрементирует указатель на неизвестный объект не может быть признан компилятором sad.gif
aesok
Цитата(zltigo @ May 2 2008, 12:38) *
Без "кажется" - компилятор который молча инкрементирует указатель на неизвестный объект не может быть признан компилятором sad.gif


А если не молча, если про это в документации написано... smile.gif
Цитата
Arithmetic on void- and Function-Pointers
In GNU C, addition and subtraction operations are supported on pointers to void and on pointers to functions. This is done by treating the size of a void or of a function as 1.

A consequence of this is that sizeof is also allowed on void and on function types, and returns 1.

The option `-Wpointer-arith' requests a warning if these extensions are used.


Анатолий.
zltigo
Цитата(aesok @ May 2 2008, 10:44) *
А если не молча, если про это в документации написано... smile.gif

Мрак. Хоть ключик под warning имеет место быть.
ReAl
Цитата(zltigo @ May 2 2008, 11:38) *
Имя компилятора в студию smile.gif
...
Без "кажется" - компилятор который молча инкрементирует указатель на неизвестный объект не может быть признан компилятором sad.gif
Ну, несколько я понимаю, "неопределённое поведение" означает, что компилятор может (имеет право) что-то сделать так или сяк, а молча ли он это сделает - зависит от включенного уровня предупреждений.
Учитывая

Сейчас проверил
Код
i = *((int*)ptr++);
gcc в данном месте независимо от размера объекта, к указателю на который делается приведение, увеличивает ptr на 1 (его право в случае UB), если есть ключ --pedantic, то выдаёт
Цитата
warning: wrong type argument to increment
Просто -Wall -Wextra для этого мало (для предупреждения на i = i++ их достаточно).

Цитата(aesok @ May 2 2008, 11:44) *
А если не молча, если про это в документации написано... smile.gif
...
Цитата
The option `-Wpointer-arith' requests a warning if these extensions are used.
Хм, я был уверен, что -Wall -Wextra включают все предпреждения...
Надо ещё раз перед сном перечитать документацию :-)
Олег.
Я пробовал в Borland C++ Builder6, там всё проходит.. Конечно эту ситуацию можно обойти очень легко.. .Просто я вроде явно привожу указатель ок определённому типу информирую компилятор о том на объект какого типа он ссылается причём приведение типа срабатывает а вот инкремент нет.. причем дело тут не в void* можно указатель описать и как char* результат будет тот же. Не исключаю что косяк в моей голове.. Просто иногда очень удобено описать указатель на массив как void* и по ходу дела приводить и его к разным типам разыменовывая содержимое буферного массива чтобы не создавать кучу разных указателей на разные типы данных.. Опять же не утверждаю и не настаиваю что я прав я вполне могу ошибаться. smile.gif . Спасибо.
zltigo
Цитата(ReAl @ May 2 2008, 11:02) *
gcc в данном месте независимо от размера объекта, к указателю на который делается приведение, увеличивает ptr на 1

Ну и разве это нормально?
Цитата
если есть ключ --pedantic, то выдаёт Просто -Wall -Wextra для этого мало..

Это плохо sad.gif. Действия с серьезными последствиями производятся и при этом даже warning на эти действия надо с дополнительными усилиями извлекать.
Попробовал
Код
b[0] = *((unsigned long int *)ptr++);

два моих самых используемых компилятора (IAR и Watcom ) выдали однозначную ошибку. Меня это очень порадовало - сам я такое не напишу - рука не поднимется, а вот чужого кода (очень разного качества sad.gif )достаточно много быват просматривать/править/использовать приходится и лишние проблемы совсем никчему.
umup
можно так :
Код
b[0] = *(unsigned long *)ptr; ptr += sizeof(a[0]);
b[1] = *(unsigned long *)ptr; ptr += sizeof(a[0]);

или так :
Код
b[0] = ((unsigned long *)ptr)[0];
b[1] = ((unsigned long *)ptr)[1];
Олег.
Да конечно так можно.. Я понимаю.. До меня дошло что к моменту взятия постинкремента тип к которому было приведение уже теряется и поэтому операция постинкремента уже не корректна. Интересно то что в Borlande приведение типа видимо сохраняется и далее постинкремент работает правильно в соответствии с приведённым типом.. Не буду спорить правильно ли это или нет но влюбом случае такой приём надо избегать.. я не собираюсь использовать это в коде проекта... smile.gif Просто столкнулся с разным поведением компиляторов при разборе синтаксиса пусть даже и не корректного..В любом случае благодарю за обсуждение. Спасибо.
777777
Цитата(Олег. @ May 2 2008, 13:36) *
Не исключаю что косяк в моей голове..

А почему бы не сделать сразу этот указатель на unsigned long - и не придется ничего преобразовывать?
Цитата(Олег. @ May 2 2008, 13:36) *
Просто иногда очень удобено описать указатель на массив как void* и по ходу дела приводить и его к разным типам разыменовывая содержимое буферного массива чтобы не создавать кучу разных указателей на разные типы данных..

Ты пишешь на Си, а думаешь на ассемблере, и пытаешься помочь компилятору. Сделай над собой усилие и забудь об ассемблере - напиши программу, думая только об алгоритме. С твоей стороны помощь компилятору может заключаться в том, чтобы локализовать использование каждой переменной - объявлять ее только в той области видимости, где она используется, и минимизировать количество глобальных переменных.
Олег.
Да Вы в общем то правы. Да я пишу на С но всегда копаюсь в скомпилированом коде ассемблера. Как ни крути ну сильна привычка и любовь к железу.. пока не могу оторваться ибо до этого много работал на ассемблере. С приобретением опыта программирования на С думаю это пройдет но пока стараюсь и учусь. Спасибо.
777777
Цитата(Олег. @ May 2 2008, 19:55) *
Да Вы в общем то правы. Да я пишу на С но всегда копаюсь в скомпилированом коде ассемблера.

Безусловно, в скомпилированном коде копаться нужно - все-таки не всегда код генерится достаточно эффективный, и когда компилятор создает что-то неудачное, программу надо менять. Но не таким варварским образом.
Andreas1
поскольку только осваиваюсь в сях, а ситуация интересная, посоветовался с опытным человеком
Цитата
извините, но эта попытка инкрементировать временный объект....
*((T*) p)++
это на самом деле
T* tmp = (T*) p;
*tmp++
......Они все таки временные переменные сделали r-value...
а по старому стандарту это все законные выражения....

правильно это делается так
*(*(T**)&p)++

И действительно
b[0] = *(*(unsigned long int **)&ptr)++;
не вызвал ошибки и , судя по листингу, правильно инкрементировал ptr.
Правда пока не понял в чем разница 07.gif
AHTOXA
Цитата(Andreas1 @ May 2 2008, 23:12) *
И действительно
b[0] = *(*(unsigned long int **)&ptr)++;
не вызвал ошибки и , судя по листингу, правильно инкрементировал ptr.
Правда пока не понял в чем разница 07.gif


Это то же, что предлагал ReAl немного выше. Разница в том, что в этом случае временный объект - указатель на наш указатель, и модифицируется не он (не временный объект), а то, на что он указывает (о как мощно задвинул:-) )
В принципе это тоже не комильфо, но работает.
ReAl
Цитата(Andreas1 @ May 2 2008, 20:12) *
поскольку только осваиваюсь в сях, а ситуация интересная, посоветовался с опытным человеком
Что значит "по старому стандарту"?
По K&R первого издания, являвшимся "стандартом де-факто", но не "де-юре"?
Я же привёл выше цитату из стандарта 1989 года про то, что cast не есть lvalue.

Другое дело, что многие компиляторы до конца 90-ых (а борлданд - так похоже и до середины 2010-ых :-) ) так и не удосужились привести себя в соответствие стандарту 1989 года, как и сейчас не все и не всё делают согласно стандарту 1999 года...

Цитата(AHTOXA @ May 2 2008, 20:35) *
Это то же, что предлагал ReAl немного выше.
Это то де по сути, но прозрачнее записано.
То, что написал я - можно подрихтовать, вставив вместо sizeof свой макрос, который как-то корректирует размер - например, в зависимости от параметра, задающего выравнивание.
У того же борланда, насколько я помню, так сделан макрос (а не __builtin) VA_ARG (stdarg.h).
Alex03
А если так:

Код
unuon
{
    char *pc;
    int *pi;
    long *pl;
} u;

u.pc = &buff;

char c = *u.pc++;
int i = *u.pi++;
long l = *u.pl++;
777777
Цитата(Alex03 @ May 5 2008, 16:01) *
А если так:

Код
unuon
{
    char *pc;
    int *pi;
    long *pl;
} u;

u.pc = &buff;

char c = *u.pc++;
int i = *u.pi++;
long l = *u.pl++;


Так можно. Только зачем? Языки высокого уровня придумываются для того, чтобы избегать низкоуровневых работ, а также для обнаружения большинства ошибок на этапе компиляции. А чего мы добиваемся подобной конструкцией?
AHTOXA
Цитата(777777 @ May 6 2008, 22:53) *
Так можно. Только зачем? Языки высокого уровня придумываются для того, чтобы избегать низкоуровневых работ, а также для обнаружения большинства ошибок на этапе компиляции. А чего мы добиваемся подобной конструкцией?


Вы уже несколько раз это повторили. А нельзя ли привести пример того, как такое делается на языках высокого уровня? Скажем, вы принимаете сериализованный список переменных разного типа. То есть, принятый вами буфер выглядит следующим образом:
[число_переменных_в_пакете][номер_переменной1][переменная1][номер_переменной2][п
еременная2]...[номер_переменнойN][переменнаяN].
В зависимости от номера переменной её тип может быть int, char, double, long int.
Научите, как это делается на языках высокого уровня?
777777
Цитата(AHTOXA @ May 6 2008, 22:45) *
А нельзя ли привести пример того, как такое делается на языках высокого уровня? Скажем, вы принимаете сериализованный список переменных разного типа. То есть, принятый вами буфер выглядит следующим образом:
[число_переменных_в_пакете][номер_переменной1][переменная1][номер_переменной2][п
еременная2]...[номер_переменнойN][переменнаяN].
В зависимости от номера переменной её тип может быть int, char, double, long int.
Научите, как это делается на языках высокого уровня?


Очень просто. (Правда, номер переменной здесь не нужен - обычно порядок определяет назначение переменной). Можно написать функцию read(void* buf, uint8_t size) которая "принимает" (неважно откуда) данные размера size. Тогда мы пишем:

int var1;
char var2;
double var3;
long var4;
read(&var1, sizeof(var1));
read(&var2, sizeof(var2));
read(&var3, sizeof(var3));
read(&var4, sizeof(var4));

или еще проще:

struct VAR {
int var1;
char var2;
double var3;
long var4;
} var;
read(&var, sizeof(var));

Только здесь надо позаботиться чтобы переменные в стуктуре были выровнены побайтно. А самой функции read нет необходимости знать о типах - она читает только заданное количество байт.

Содержимое функции read зависит от того, откуда ей принимать данные, в любом случае ей придется инкрементировать указатель *buf. Если компилятор допускает ++ для типа void* и увеличивает его именно на 1, то такой код годится, если нет (как того требует стандарт) - то прототип функции должен быть read(char* buf, uint8_t size), но тогда при обращении к ней нужно приводить тип буфера к char*
AHTOXA
Цитата(777777 @ May 7 2008, 10:18) *
Очень просто. (Правда, номер переменной здесь не нужен - обычно порядок определяет назначение переменной).


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

Цитата
Можно написать функцию read(void* buf, uint8_t size) которая "принимает" (неважно откуда) данные размера size.


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

Цитата
Содержимое функции read зависит от того, откуда ей принимать данные, в любом случае ей придется инкрементировать указатель *buf. Если компилятор допускает ++ для типа void* и увеличивает его именно на 1, то такой код годится, если нет (как того требует стандарт) - то прототип функции должен быть read(char* buf, uint8_t size), но тогда при обращении к ней нужно приводить тип буфера к char*


Не вижу здесь никакого особого "высокоуровневого" подхода.
777777
Цитата(AHTOXA @ May 7 2008, 08:54) *
Собственно весь вопрос и заключался в том, как должна быть написана эта функция. Вы функцию не привели, и идеи ваши никуда не ушли в сторону от банальной арифметики с указателями.

Вы тоже не объяснили откуда плучаются данные.

void read(char* p, uint8_t n)
{
while(n--)
*p++ = getchar();
}

Каков вопрос, таков ответ smile.gif

А вообще, вопрос надо задавать более широко: что требуется в конечном итоге? Тогда, возможно, отпадет даже сама необходимость решать этот частный вопрос.
AHTOXA
Цитата(777777 @ May 7 2008, 11:00) *
Вы тоже не объяснили откуда плучаются данные.


А какая разница?

Цитата
void read(char* p, uint8_t n)
{
while(n--)
*p++ = getchar();
}


Спасибо, теперь я наконец-таки узнал, как надо писать на языке высокого уровня biggrin.gif
Сергей Борщ
Цитата(AHTOXA @ May 7 2008, 08:03) *
Спасибо, теперь я наконец-таки узнал, как надо писать на языке высокого уровня
Можно я чуток поправлю?
Любой указатель приводится к void* автоматически. В исходном коде компилятор не дал бы read(&var3, sizeof(var3)); только read((char *)&var3, sizeof(var3)):
Код
void read(void* pDst, uint8_t size)
{
    uint8_t *p = (uint8_t*)pDst;
    while(size--)
        *p++ = getchar();
}

Еще можно предположить, что размер переменной не может быть равным нулю. Тогда:
Код
void read(void* pDst, uint8_t size)
{
    uint8_t *p = (uint8_t*)pDst;
    do
        *p++ = getchar();
    while (--size);
}
Ну и отсюда всего один шаг до С++:
Код
template <typename T> void read(T & Dst)
{
    uint8_t *p = (uint8_t*)&Dst;
    do
        *p++ = getchar();
    while (--size);
}

read(var1);
read(var2);
read(var3);
Или, если экономим код, то
Код
void read(void* pDst, uint8_t size)
{
    uint8_t *p = (uint8_t*)pDst;
    do
        *p++ = getchar();
    while (--size);
}
template <typename T> void read(T & Dst)
{
    read(&T, sizeof(T));
}
777777
Цитата(Сергей Борщ @ May 7 2008, 10:48) *
Можно я чуток поправлю?
Любой указатель приводится к void* автоматически. В исходном коде компилятор не дал бы read(&var3, sizeof(var3)); только read((char *)&var3, sizeof(var3)):
Код
void read(void* pDst, uint8_t size)
{
    uint8_t *p = (uint8_t*)pDst;
    while(size--)
        *p++ = getchar();
}

Здесь создается новая переменная и pDst в нее копируется. Лучше инкрементировать саму pDst, ради этого стоит сделать ее char* и каждый раз при обращении писать преобразование типа. А если компилятор (вопреки стандарту) позволяет инкремент void* то можно оставить ее и void*
Цитата(Сергей Борщ @ May 7 2008, 10:48) *
Еще можно предположить, что размер переменной не может быть равным нулю.

А вот таких предположений лучше не делать и позволить функции не читать ни одного символа. Такое возможно если код генерируется из шаблонов, а их параметры зависят о каких-либо условий. Тогда 0, оказавшийся в размере, приведет к тому, что компилятор просто выкинет никогда не исполняющийся код.
Цитата(Сергей Борщ @ May 7 2008, 10:48) *
Ну и отсюда всего один шаг до С++:
Код
template <typename T> void read(T & Dst)
{
    uint8_t *p = (uint8_t*)&Dst;
    do
        *p++ = getchar();
    while (--size);
}

size потерялся где-то. Вероятно нужен еще sizeof(T)
Цитата(Сергей Борщ @ May 7 2008, 10:48) *
Или, если экономим код, то
Код
...
template <typename T> void read(T & Dst)
{
    read(&T, sizeof(T));
}

Если уж нас заботит экономия кода, то с шаблошами лучше не связываться... smile.gif
AHTOXA
Цитата(Сергей Борщ @ May 7 2008, 12:48) *
Можно я чуток поправлю?


Да это всё понятно:-) Я ждал какого-то мегаоткровения, а тут... :-) Так и не понял, где здесь обещанные преимущества языка высокого уровня, по сравнению, скажем, с вариантом от Редчука. Контроль типов - на программисте как был, так и остался, арифметика с указателями - как была, так и осталась. Зато столько пафоса...

Ваш, Сергей, вариант с шаблонами по крайней мере переносит часть контроля типов на компилятор.
ReAl
Цитата(777777 @ May 7 2008, 10:38) *
Если уж нас заботит экономия кода, то с шаблошами лучше не связываться... smile.gif
Если нас заботит экономия кода, то любой иснтрумент надо применять аккуртано. Многие монстроидальные результаты нынешнего софтостроения связаны не с примененеим С++ вообще или шаболнов в частности, а с бездумным "оттянул-уронил".
Я бы, может, и не влез, если бы не эта последняя фраза, которую я процитировал первой.

Цитата(777777 @ May 7 2008, 10:38) *
Здесь создается новая переменная и pDst в нее копируется. Лучше инкрементировать саму pDst, ради этого стоит сделать ее char* и каждый раз при обращении писать преобразование типа.
Никто ничего нового не создаёт.
К моменту использования нового указателя старый уже никому не нужен. Как следствие, его можно "выбросить", а регистры использовать по новой. Чудесным образом, в них уже есть нужное значение и ничего никуда копировать вообще не нужно (ну, за исключением копирования указателя из регистров передачи параметра в указательный регистр, у MSP430 и ARM не было бы и этого).
"Найдите десять отличий"
CODE
#include <stdint.h>
uint8_t get_byte();

void read(void* pDst, uint8_t size)
{
uint8_t *p = (uint8_t*)pDst;
while(size--)
*p++ = get_byte();
}

read:
push r17
push r28
push r29
mov r17,r22
movw r28,r24
rjmp .L2
.L3:
rcall get_byte
st Y+,r24
.L2:
subi r17,1
brcc .L3
pop r29
pop r28
pop r17
ret

CODE
#include <stdint.h>

uint8_t get_byte();

void read(uint8_t* pDst, uint8_t size)
{
while(size--)
*pDst++ = get_byte();
}

read:
push r17
push r28
push r29
movw r28,r24
mov r17,r22
rjmp .L2
.L3:
rcall get_byte
st Y+,r24
.L2:
subi r17,1
brcc .L3
pop r29
pop r28
pop r17
ret
Я нашёл только одно - в порядке копирования аргументов на новые места (впрочем, size можно было и не копировать, но это не имеет отношения к преобразованибю типов и "созданиб нового указателя").

Цитата(777777 @ May 7 2008, 10:38) *
Если уж нас заботит экономия кода, то с шаблошами лучше не связываться... smile.gif
Вернёмся. Для более правдоподобного сравнивания я в каждом варианте прочту каждый вид переменной по нескольку раз.
Чистый С, "без никаких латинских примесей"

CODE
#include <stdint.h>
uint8_t get_byte(void);

// уже выяснили, что тут uint8_t * pdst никакого выигрыша не даёт,
// а void даёт возможность применть макрос READ.
void read(void *pdst, uint8_t n)
{
uint8_t *p = (uint8_t*)pdst;
while(n--)
*p++ = get_byte();
}

#define READ(A) read( &(A), sizeof(A) )

uint8_t c0, c1, c2, c3;
uint16_t w0, w1, w2, w3;
uint32_t d0, d1, d2, d3;

void foo(void)
{
READ( c0);
READ( w0);
READ( d0);
READ( d1);
READ( w1);
READ( c1);
}

void moo(void)
{
READ( c0);
READ( c1);
READ( w0);
READ( w1);
READ( d0);
READ( d1);
}


CODE
avr-gcc -Os -mmcu=atmega88 -S
/* странно, отчёт компилятора там завысил размер этой функции, а тут занизил */
read: /* 14 */
push r17
push r28
push r29
mov r17,r22
movw r28,r24
rjmp .L2
.L3:
rcall get_byte
st Y+,r24
.L2:
subi r17,1
brcc .L3
pop r29
pop r28
pop r17
ret
/* function read size 13 (6) */

moo:
ldi r22,lo8(1)
ldi r24,lo8(c0)
ldi r25,hi8(c0)
rcall read
ldi r22,lo8(1)
ldi r24,lo8(c1)
ldi r25,hi8(c1)
rcall read
ldi r22,lo8(2)
ldi r24,lo8(w0)
ldi r25,hi8(w0)
rcall read
ldi r22,lo8(2)
ldi r24,lo8(w1)
ldi r25,hi8(w1)
rcall read
ldi r22,lo8(4)
ldi r24,lo8(d0)
ldi r25,hi8(d0)
rcall read
ldi r22,lo8(4)
ldi r24,lo8(d1)
ldi r25,hi8(d1)
rcall read
ret
/* function moo size 25 (24) */

foo:
ldi r22,lo8(1)
ldi r24,lo8(c0)
ldi r25,hi8(c0)
rcall read
ldi r22,lo8(2)
ldi r24,lo8(w0)
ldi r25,hi8(w0)
rcall read
ldi r22,lo8(4)
ldi r24,lo8(d0)
ldi r25,hi8(d0)
rcall read
ldi r22,lo8(4)
ldi r24,lo8(d1)
ldi r25,hi8(d1)
rcall read
ldi r22,lo8(2)
ldi r24,lo8(w1)
ldi r25,hi8(w1)
rcall read
ldi r22,lo8(1)
ldi r24,lo8(c1)
ldi r25,hi8(c1)
rcall read
ret
/* function foo size 25 (24) */

/* File "f1.c": code 63 = 0x003f ( 54), prologues 3, epilogues 6 */
/* 64 */


Гадкие, противные, местожрущие шаблоны.
CODE
#include <stdint.h>

uint8_t get_byte();

void read(void* pDst, uint8_t size)
{
uint8_t *p = (uint8_t*)pDst;
do
*p++ = get_byte();
while (--size);
}

template <typename T> void read(T & Dst)
{
read(&Dst, sizeof(T));
}

uint8_t c0, c1, c2, c3;
uint16_t w0, w1, w2, w3;
uint32_t d0, d1, d2, d3;

void foo(void)
{
read( c0);
read( w0);
read( d0);
read( d1);
read( w1);
read( c1);
}


void moo(void)
{
read( c0);
read( c1);
read( w0);
read( w1);
read( d0);
read( d1);
}


CODE
_Z4readPvh: /* 13 */
push r17
push r28
push r29
mov r17,r22
movw r28,r24
.L2:
rcall _Z8get_bytev
st Y+,r24
subi r17,lo8(-(-1))
brne .L2
pop r29
pop r28
pop r17
ret
/* function void read(void*, uint8_t) size 14 (7) */



_Z4readImEvRT_: /* 3 */
ldi r22,lo8(4)
rcall _Z4readPvh
ret
/* function void read(T&) [with T = uint32_t] size 3 (2) */


_Z4readIjEvRT_: /* 3 */
ldi r22,lo8(2)
rcall _Z4readPvh
ret
/* function void read(T&) [with T = uint16_t] size 3 (2) */


_Z4readIhEvRT_: /* 3 */
ldi r22,lo8(1)
rcall _Z4readPvh
ret
/* function void read(T&) [with T = uint8_t] size 3 (2) */


_Z3moov: /* 19 */
ldi r24,lo8(c0)
ldi r25,hi8(c0)
rcall _Z4readIhEvRT_
ldi r24,lo8(c1)
ldi r25,hi8(c1)
rcall _Z4readIhEvRT_
ldi r24,lo8(w0)
ldi r25,hi8(w0)
rcall _Z4readIjEvRT_
ldi r24,lo8(w1)
ldi r25,hi8(w1)
rcall _Z4readIjEvRT_
ldi r24,lo8(d0)
ldi r25,hi8(d0)
rcall _Z4readImEvRT_
ldi r24,lo8(d1)
ldi r25,hi8(d1)
rcall _Z4readImEvRT_
ret
/* function void moo() size 19 (18) */

_Z3foov: /* 19 */
ldi r24,lo8(c0)
ldi r25,hi8(c0)
rcall _Z4readIhEvRT_
ldi r24,lo8(w0)
ldi r25,hi8(w0)
rcall _Z4readIjEvRT_
ldi r24,lo8(d0)
ldi r25,hi8(d0)
rcall _Z4readImEvRT_
ldi r24,lo8(d1)
ldi r25,hi8(d1)
rcall _Z4readImEvRT_
ldi r24,lo8(w1)
ldi r25,hi8(w1)
rcall _Z4readIjEvRT_
ldi r24,lo8(c1)
ldi r25,hi8(c1)
rcall _Z4readIhEvRT_
ret
/* function void foo() size 19 (18) */

/* File "f2.cpp": code 61 = 0x003d ( 49), prologues 3, epilogues 9 */
/* 60 */

Итого вариант с шаблонами версии "если хочеться экономить код" дал выигрыш в 3 слова программной памяти (лишний rjmp в функции read C-шного варианта не считаем) . Или, на этом участке, около 5%.
Обратите внимание - "проигрыш" шаблонов в сгенерированных при специализации шаблона функциях (9 слов), а выигрыш - при каждом применении шаблонного чтения (1 слово, тут 12 применений, 3 слова выигрыша). И чем больше будет чтений, тем больше будет выигрыш.
"догнать" на С можно, заведя самостоятельно функции и производя "закат солнца вручную".
Перегнать не выйдет.
CODE
#include <stdint.h>

uint8_t get_byte(void);

void read(void *pdst, uint8_t n)
{
uint8_t *p = (uint8_t*)pdst;
while(n--)
*p++ = get_byte();
}

void read_u8(uint8_t *p) { read(p, sizeof(uint8_t) ); }
void read_u16(uint16_t *p) { read(p, sizeof(uint16_t) ); }
void read_u32(uint32_t *p) { read(p, sizeof(uint32_t) ); }

uint8_t c0, c1, c2, c3;
uint16_t w0, w1, w2, w3;
uint32_t d0, d1, d2, d3;

void foo(void)
{
read_u8( &c0);
read_u16( &w0);
read_u32( &d0);
read_u32( &d1);
read_u16( &w1);
read_u8( &c1);
}


void moo(void)
{
read_u8( &c0);
read_u8( &c1);
read_u16( &w0);
read_u16( &w1);
read_u32( &d0);
read_u32( &d1);
}
Сергей Борщ
Цитата(777777 @ May 7 2008, 10:38) *
А вот таких предположений лучше не делать и позволить функции не читать ни одного символа. Такое возможно если код генерируется из шаблонов, а их параметры зависят о каких-либо условий. Тогда 0, оказавшийся в размере, приведет к тому, что компилятор просто выкинет никогда не исполняющийся код.
Эту проверку можно сделать перед вызовом функции, когда принимается решение - в какую переменную читать. Ведь проверка все равно нужна, а код цикла do {} while(--size) будет короче чем while(size--) {} и в байтах кода и в тактах.
Цитата(777777 @ May 7 2008, 10:38) *
size потерялся где-то. Вероятно нужен еще sizeof(T)
Да, конечно. надо было еще добавить uint8_t size = sizeof(T);
Ну а про шаблоны ReAl все изложил с фактами наперевес. Добавлю лишь, что можно добиться на выходе точно такого же кода, как и в С-варианте, если сделать шаблонную функцию inline. А преимущество будет в том, что не ошибешься, указав имя одной переменной а sizeof другой. Пусть рутину выполняет компилятор.
alexander55
Цитата(Олег. @ May 2 2008, 08:17) *
unsigned long int a[] = {1, 2, 3, 4, 5};
unsigned long int b[5];

void* ptr = a;

b[0] = *((unsigned long int*)ptr)++;
b[1] = *((unsigned long long int*)ptr)++;


unsigned long int a[] = {1, 2, 3, 4, 5};
unsigned long int b[5];

unsigned long * ptr = a;

b[0] = (*ptr)++; // или же м.б. b[0] = *ptr++; или b[0] = *(ptr++); Что Вы хотите - Вам виднее.
b[1] = *ptr++;
PS. Все современные языки борются с указателями неявного типа. Это источник глюков.



Цитата(zltigo @ May 2 2008, 12:38) *
Без "кажется" - компилятор который молча инкрементирует указатель на неизвестный объект не может быть признан компилятором sad.gif

И я о том же.

Цитата(777777 @ May 2 2008, 18:58) *
А почему бы не сделать сразу этот указатель на unsigned long - и не придется ничего преобразовывать?

Наверное, для науки надо (как делать не надо). biggrin.gif
777777
Цитата(ReAl @ May 7 2008, 13:30) *
Никто ничего нового не создаёт.
К моменту использования нового указателя старый уже никому не нужен. Как следствие, его можно "выбросить", а регистры использовать по новой.

Все это конечно так, но компилятор не знает о том, что старый аргумент никому не нужен, поэтому в общем случае ему придется создавать новую еременную. Правда, в этом конкретном случае он проявил чудеса догадливости и действительно не стал ее создавать, но я не уверен что так будет если функция окажется большой и сложной.
Цитата(ReAl @ May 7 2008, 13:30) *
...
// а void даёт возможность применть макрос READ.

а при чем тут макрос? Ничего общего с шаблоном у него нет.
Цитата(ReAl @ May 7 2008, 13:30) *
Для более правдоподобного сравнивания я в каждом варианте прочту каждый вид переменной по нескольку раз.

Я извиняюсь, но это сделано не для "более правдоподобного сравнивания", а для того, чтобы вариант с шаблонами оказался короче smile.gif Достаточно оставить 5 чтений вместо 6 - и результат резко меняется. Но и с шестью чтениями, стоит добавить еще один тип данных - и он вновь окажется длинее за счет появления еще одной инстанцированной функции.
Цитата(ReAl @ May 7 2008, 13:30) *
Если нас заботит экономия кода, то любой иснтрумент надо применять аккуртано. Многие монстроидальные результаты нынешнего софтостроения связаны не с примененеим С++ вообще или шаболнов в частности, а с бездумным "оттянул-уронил".

С другой стороны, если при их применении нужно тщательное измерение результатов (ведь они так резко меняются от количества вызовов и количества типов) то на фига они нужны - лучше уж быстренько написать что требует заказчик на старом отработанном инструменте - время программиста стоит дороже.
ReAl
Цитата(777777 @ May 7 2008, 14:46) *
Все это конечно так, но компилятор не знает о том, что старый аргумент никому не нужен, поэтому в общем случае ему придется создавать новую еременную. Правда, в этом конкретном случае он проявил чудеса догадливости и действительно не стал ее создавать, но я не уверен что так будет если функция окажется большой и сложной.

Он не догадывается, он знает. Он, знаете ли, отслеживает использование каждой переменной и при необходимости использует регистры, которые занимала одна переменная, для другой.
Да, на сложных функциях очень много вариантов размещения и оптимизатор не всегда выберет идеальный, но на сложных функциях и человек не всегда всё сообразит.

Цитата(777777 @ May 7 2008, 14:46) *
а при чем тут макрос? Ничего общего с шаблоном у него нет.
А я разве сказал, что у него есть что-то общее с шаблоном? 07.gif
С ним чисто С-шный текст быстрее пишется и меньше шансов ошибиться, подсунув не тот sizeof. Вот и всё.


Цитата(777777 @ May 7 2008, 14:46) *
Я извиняюсь, но это сделано не для "более правдоподобного сравнивания", а для того, чтобы вариант с шаблонами оказался короче smile.gif Достаточно оставить 5 чтений вместо 6 - и результат резко меняется. Но и с шестью чтениями, стоит добавить еще один тип данных - и он вновь окажется длинее за счет появления еще одной инстанцированной функции.
Зависит от построения программы, конечно, но если Вам более правдоподобными кажутся программы, в которых из потока вынимается в одном единственном месте по одной единственной переменной каждого типа, то да, в Вашем случае шаблоны проиграют. В случае данной платформы - по два слова программной памяти на каждом типе. Это, безусловно, катастрофическая величина, позволяющая говорить "если важен размер, с шаблонами лучше не связываться".
Уже при трёх чтениях переменной каждого типа шаблонный вариант не проигрывает.
На вполне вероятных количествах чтений 4 и больше - выигрывает. Выигрывает не столько, чтобы плясать от радости, но я только хотел показать, что "слухи о безусловной вредности шаблонов для комплекции программы несколько преувеличены", при количествах чтений от 1 до 5 на тип - 50/50. И даже в случае проигрыша - он чепуховый.

Цитата(777777 @ May 7 2008, 14:46) *
С другой стороны, если при их применении нужно тщательное измерение результатов (ведь они так резко меняются от количества вызовов и количества типов) то на фига они нужны - лучше уж быстренько написать что требует заказчик на старом отработанном инструменте
С третьей стороны, если размер так важен, что нужно столь "тщательное измерение результатов", то либо всю программу надо писать на ассемблере, либо это то, что на фотофорумах называют техноонанизмом.

Цитата(777777 @ May 7 2008, 14:46) *
время программиста стоит дороже.
Вы опредедлитесь. А то писать вручную приведение к char* в каждом вызове, писать вручную размеры переменных (ведь макросы тут ни при чём) с шансом где-то ошибиться - это не страшно, подумаешь, время программиста, главное - инструмент старый и отработанный.
Я, кстати, и не тратил бы время на эти измерения, если бы не Ваше столь же голословное, сколь и громкое утверждение.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.