LinuxForum.Fans
Тема

Automatic vs static

Автор: Peter 54 сообщений 6192 просмотров
#226391

Здравствуйте!

Хотелось бы понять, в чём разница при обращении по указателю к переменным классов
automatic и static.

Именно, есть пременная типа int, 32-х разрядная и хотелось непосредственно получить
доступ к её двум старшим байтам. В тестовой программе main я сделал так:

text

int val;
short int *pv = (short int*)((void*)&val +2);

Я знаю, что это плохо и так делать не следует, но сейчас у меня вопрос в другом.
Если теперь я меняю значение val и печатаю результат через pv, то и в 1-й и во 2-й
разы выводятся правильные значения.

В рабочей программе из main вызывается одна подпрограмма, из неё другая и в ней
я попробовал сделать то же самое с внутренней переменной, т.е класса automatic.
Оказалось, что правильное значение выводится только в 1-й раз, а в следующие,
несмотря на изменение val, продолжает выводиться то, что вывелось в 1-й раз, словно
бы эта val куда-то скопировалась и указатель установился на скопированное значение.
То же самое, если val маходится в области памяти, выделенной malloc.

Если же val внутри 2-й подпрограммы объявить как static, то при обращении к ней
через pv выводятся правильные значения. Там в цикле несколько раз делается
обращение к pv.

Если в 1-й подпрограмме объявить переменную val как static и передавать 2-й
её адрес и устанавливать pv относительно него

text

short int *pv = (short int*)((void*)pval +2);

то тоже всё работает.

Если же в 1-й подпрограмме val имеет класс automatic и во 2-й используется её адрес,
то наблюдается непонятное явление:
когда во 2-й подпрограмме после обращений к pv стоит отладочная печать,
то тоже всё работает. Если же печать отключить, то происходят сбои, что видно
по результатам работы программы в целом - она восстанавливает изображение из jpg-файла
и в этом случае картинка попорченная, а с печатью нормальная.

В чём тут причина? 2-я подпрограмма за врема работы 1-й вызывается 72000 раз,
сама программа работает секунд 20 или около, т.к. там очень большой отладочный
вывод, десятки мегабайт.

Ведь пока 2-я подпрограмма работает, выделенные ей страницы памяти вряд ли
куда-то перемещаются, а сама она быстрая. Почему же уже при 2-м обращении к pv,
если он получен из адреса объявленной в ней val класса automatic, результат неверный?
Это не зависит от того, инициализируется ли val при объявлении или нет.
Программа однопоточная, процессор четырёх-ядерный.

Использование переменной static не подходит, т.к. с ней работа замедляется,
а мне всё это надо, чтобы ускорить. Это в Ubiuntu 10.10.
Т.е. непонятно, какая в даном случае разница при объявленни во 2-й подпрограмме
val как static или automatic, ведь пока она не закончилась, все переменные ведь
сохраняются.

Пётр.

#226408

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

Peter написал:
Использование переменной static не подходит, т.к. с ней работа замедляется,
а мне всё это надо, чтобы ускорить. Это в Ubiuntu 10.10.

не должно такого быть... вы с оптимизацией собираете?

ЗЫ: что за automatic? вы так стековую память обозвали? и речь о си?

#226413
Fat-Zer написал:
Peter, у меня почему-то от ваших постов всегда извилины в бутылку клейна заворачивались. минимальный неработающий код покажите... уж очень трудно всё это описание воспринимать....

[quote=Peter]Использование переменной static не подходит, т.к. с ней работа замедляется,
а мне всё это надо, чтобы ускорить. Это в Ubiuntu 10.10.

не должно такого быть... вы с оптимизацией собираете?

ЗЫ: что за automatic? вы так стековую память обозвали? и речь о си?[/quote]
Да, я имел в виду стековую память подпрограмы, она на C.
Компилируется с ключом "-O3", но я думаю, что причина не в этом. Правильно написанная
программа должна работать всегда.

Там нечего особенно приводить. Скажем, в тестовой main меняю значение val, вывожу *pv
потом повторяю это - оба раза вывод правильный. Делаю то же самое в рабочей программе
в подпрограмме, которую я назвал 2-й - вывод правильный только в 1-й раз. Например, если
сделать это до входа в цикл, в котором используется pv, то внутри цикла постоянно
выводится значение, присовенное val до входа в цикл, хотя сама val внутри цикла меняется.
Я пробовал описывать указатель pv как inline, но ничего не изменилось, только предупреждение
об этом было при компиляции.

Если же val объявлена как static, то pv указывает на правильное значение, но при этом
программа работает дольше.

Не знаю, как это объчснить. val меняется в любом случае, на что же тогда указывает pv,
когда val находится в стековой памяти?

Пётр.

#226418

с вероятностью 98% любая магия в программе это идиотская ошибка в коде... с вероятностью 1,999999... - не идиотская ошибка. остальное приходится на ошибки в процессорах.

Peter написал:
Правильно написанная программа должна работать всегда.

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

Peter написал:
Там нечего особенно приводить.

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

Peter написал:
Скажем, в тестовой main меняю значение val, вывожу *pv потом повторяю это - оба раза вывод правильный.

т.е. в тестовой всё работает? значит нужно подтягивать её до основной, пока не перестанет...

ЗЫ: на всякий пожарный: многопоточности нет, дело происходит на x86/64?

#226462

Да, программа однопоточная, x86, система 32-разрядная.
Тестовая программа никакого отношения к рабочей не имеет, это просто небольшая main,
в которой я иногда проверяю отдельные конструкции. Сейчас попробовал ещё раз,
с объявлением

text

int val;
short int *pv = (short int*)(void*)&val + 1;

несколько раз меняю val и вывожу *pv, выводится правильно.
В рабочей программе, во 2-й подпрограмме при таком же объявлении по-прежнему правильно
выводится только 1-е значение, присвоенное val, а далее продолжает выводиться оно же,
несмотря на изменение val.

Я тоже думаю, что ошибка у меня, до сих пор всегда так и бывало.
По-видимому, я чего-то не знаю.

Пётр.

#226467

Кстати, если в тестовой программе эту конструкцию поместить в подпрограмму, которая только
и делает, что меняет val и выводит *pv, то там то же самое, причём не выводится и 1-е
значение, присвоенное val, а постоянно выводятся нули.

Пётр.

#226480

Я нашёл временный выход - если в подпрограмме распечатать значение указателя,
то потом при обращении по нему получаются правильные значения.
Я распечатывал значение также и указатель на саму переменную и было видно,
что указателю на short int присваивается правильное значение.
Вот работающий вариант:

text

void ch_short(){
int m,*pm;
short int *pu=(short int*)(vojd*)&m+1;
pm=&m;
fprintf(stdout,"%hX %hX\n",(int)pm,(int)pu);
m=0xC33C6009;fprintf(stdout,"%X %04hX\n",m,*pu);
m=0x3CC39006;fprintf(stdout,"%X %04hX\n",m,*pu);
m=0xA55A7EE7;fprintf(stdout,"%X %04hX\n",m,*pu);
m=0x5AA5E77E;fprintf(stdout,"%X %04hX\n",m,*pu);
return;}

Если же значение pu не выводить, то вместо старших байтов m печатаются нули.

В чём причина такого поведения, я не знаю.

Пётр.

#226533

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

#226534
Peter написал:
Кстати, если в тестовой программе эту конструкцию поместить в подпрограмму, которая только
и делает, что меняет val и выводит *pv, то там то же самое, причём не выводится и 1-е
значение, присвоенное val, а постоянно выводятся нули.

Полный код этого примера в студию

#226542
voral написал:
[quote=Peter]Кстати, если в тестовой программе эту конструкцию поместить в подпрограмму, которая только
и делает, что меняет val и выводит *pv, то там то же самое, причём не выводится и 1-е
значение, присвоенное val, а постоянно выводятся нули.

Полный код этого примера в студию[/quote]
Для этого из приведённого примера надо убрать строку, где печатаются указатели, приведённые к int.

Однако в рабочей программе проблема осталась. В ней из main вызывается 1-я подпрограмма, из неё 2-я,
но это несущественно. В ней есть объявления

text

int val;
short int *pv = (short int*)(void*)&val + 1;

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

text

int val,*p=&val;
short int *pv=(short int*)p + 1;

в цикле по j несколько раз встречается оператор

text

val = something ;
*(dec+#+j)=128+(*pv>>2);

Вместо # там константа, от 1 до 7. dec - это один из передаваемых параметров, unsigned char *dec.
Проблема в том, что, судя по результатам работы подпрограммы, это не работает, т.е. pv возвращает
одно и то же значение, независимо от состояния val, на старшие 2 байта которой указывает.

Если же после каждого этого оператора вставить отладочный вывод c *pv,
то всё работает правильно.
Это явление наблюдается при компилировании с "-O3" и "-O2", а при "-O1", как я обнаружил,
оно исчезает и подпрограмма работает правильно без отладочного вывода.

Мне это всё равно непонятно, т.к. pv - это указатель, после его установки он не изменяется,
т.е. должен адресовать переменную и никакая оптимизация для этого не требуется, так что
мне неясно, что и из каких соображений с ним делает компилятор.
Ubuntu 11.04, gcc-4:4.5.2-1ubuntu3.

Пётр.

#226554

Кстати, то же самое с проверочной подпрограммой ch_short: если main вместе с ней компилировать с "-O1",
то никаких "костылей" в виде печати указателя не требуется, работает, как я и ожидал.
Если это явление есть следствие оптимизации, то она мне непонятна.
В ch_short pv не указывает на переменную, ему просто присваивается некоторое значение,
но почему компилятор решил, что это надо интерпретировать, как указатель на const?
Ведь при "-O1" он так не считает.
Можно ли запретить оптимизацию для отдельной переменной?

Пётр.

#226574

я сам UB тут не вижу, но подтверждаю - не работает. как временный вариант - объявить m, как volatile. потом пошерстить стандарт на то, почему и есть ли тут UB. а потом, может быть, написать багрепорт в gcc.

[added] соврал с volatile результат ещё интересней...

text
C33C6009 C33C
3CC39006 C33C
A55A7EE7 C33C
5AA5E77E C33C

остаётся использовать человеческие приёмы....

#226617

Ещё я заметил, что неважно, куда в ch_sort вставить печать указателя - в начало или перед return,
но важно, чтобы это была именно печать, например, по sprintf(buf, ....). Другие варианты не работают,
например, присвоение через указатель.

Без этого именно то, что и у вас - повторяется какое-то 1-е значение.
Вообще в мире есть много загадочного и непонятного.

Пётр.

#226628

У меня компилятор сообщает, что он сбрасывает квалификатор volatile.
Однако ch_short с ним вроде бы работает правильно, а рабочая программа нет.

Пётр.

#226682

Похоже, что это баг gcc в этой версии Ubuntu, т.к. у меня в Debian Wheezy gcc-4:4.6.2-4
и тестовая программа и рабочая работают без всяких костылей с "-O3", т.е. так, как я
первоначально и хотел, и картинка восстанавливается правильно.

В этой версии Ubuntu есть ещё известный баг: если отключить автомонтирование
съёмных носителей, то при монтировании user'ом вручную не даются права на запись,
а файлы оказываются принадлежащими root.

Пётр.

#226751
Peter написал:
Правильно написанная
программа должна работать всегда.

не факт.

Peter написал:
short int *pv = (short int*)((void*)&val +2);
Я знаю, что это плохо и так делать не следует

и у вас - неправильная.

Fat-Zer написал:
я сам UB тут не вижу

void* -> type это UB.

#226752
Peter написал:
Похоже, что это баг gcc

это не баг, а фича.

#226760
drBatty написал:
void* -> type это UB.
ISO/IEC 9899:TC2 6.3.2.3 Pointers написал:
A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

не?
если это и UB, то проблема в прибавлении единицы...

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

#226761
Fat-Zer написал:
A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

речь идёт о конвертации одного и того-же типа. Например int* -> void* -> int* допустимо. А вот что-то другое - UB. Обычно работает, но не обязано. Особенно с О3.

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

угу. первый раз лет 30 назад.

#226762
Peter написал:
short int *pv = (short int*)((void*)pval +2);
Peter написал:
Использование переменной static не подходит, т.к. с ней работа замедляется,
а мне всё это надо, чтобы ускорить.

компилятор может оптимизировать данную конструкцию, что и делает. В случае static он этого сделать не может. Однако, оптимизация обязана правильно работать только если данные данные действительно short. А вот в случае int оптимизация работать правильно не обязана. Что вы и наблюдаете.

Войдите или зарегистрируйтесь, чтобы ответить.