Форум программистов, компьютерный форум, киберфорум
Наши страницы
C для начинающих
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.63/27: Рейтинг темы: голосов - 27, средняя оценка - 4.63
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
#1

[Задача] Адресная арифметика

08.03.2012, 12:38. Просмотров 4847. Ответов 49

Просьба к модераторам: НЕ надо перетаскивать в разделы типа "Си\Си++ для экспертов"

Пример возник на основе реальной программы. Пример содержит ошибку, а потому не факт, что повторится на всех компиляторах. В моём случае ошибка проявлялась на i386-linux32 при использовании компилятора gcc.

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
 
char a = 5;
char b = 10;
 
int main (void)
{
  char tmp;
 
  /* Для контроля убедимся, что "a" и "b" оказались в соседних
   * ячейках памяти. Разница указателей должна равняться единице.
   * Именно единицу мы используем в следующем операторе */
  printf ("&b - &a = %d\n", &b - &a);
 
  /* Записываем в переменную "b" */
  *(&a + 1) = 11;
 
  /* Читаем из переменной "b" */
  tmp = b;
 
  printf ("tmp = %d\n", tmp);
  printf ("b = %d\n", b);
  return 0;
}
Запуск без оптимизаций:

Код
$ gcc t.c
$ ./a.out 
&b - &a = 1
tmp = 11
b = 11
Запуск с оптимизациями:

Код
$ gcc t.c -O3
$ ./a.out 
&b - &a = 1
tmp = 10
b = 11
Вопрос. В программе перед printf'ами есть операция "tmp = b;". Почему напечатались разные значения для "tmp" и "b"? Эти значения далеко не рандомные: одно напечатанное значение соответствует "старому" значению переменной "b", а другое - "новому"

Добавлено через 29 минут
Традиционная просьба прятать догадки и ответы под CUT'ом
0
Лучшие ответы (1)
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
08.03.2012, 12:38
Ответы с готовыми решениями:

Адресная арифметика
Можно ли в C++ в массиве произвольного типа использовать адресную...

адресная арифметика
int funk(char *s) { char *p=s; while(*p) p++; ...

Адресная арифметика
Объясните пожалуйста вот эту строчку кода return allocp -n: Почему просто...

Указатели и адресная арифметика
Язык C. Задание звучит так: 2) Для этого фрагмента программы написать...

Адресная арифметика и массивы
Нужно с помощью указателя организовать ввод и вывод матрицы. Индексную...

49
NoMasters
Псевдослучайный
1909 / 1120 / 90
Регистрация: 13.09.2011
Сообщений: 3,178
08.03.2012, 17:28 #21
Evg, ладно, не буду настаивать, пожалуй таким извращение всё равно может заняться разве что llvm с её jit для С.
0
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
08.03.2012, 17:37  [ТС] #22
Цитата Сообщение от NoMasters Посмотреть сообщение
Evg, ладно, не буду настаивать, пожалуй таким извращение всё равно может заняться разве что llvm с её jit для С.
Дело тут не в извращениях, а в ошибке. Причину ошибки я описал в посте #16. Причину возникновения ошибки косвенно ты тоже указал правильно, но из разряда "я где-то слышал, но сам в этом не разбираюсь". Сомневаюсь, что разработчики llvm допустят такую же ошибку. Как показывает практика, авторы проектов, которые работают под многими разными системами, гораздо лучше разбираются во всяких тонкостях чем те, которые всю жизнь работают под одной осью на одном компиляторе
0
alex_x_x
08.03.2012, 17:38
  #23

Не по теме:

Цитата Сообщение от NoMasters Посмотреть сообщение
Evg, delta может меняться в процессе исполнения.
это значит, что адрес одной из переменных поменялся, а если так, то нужно выкинуть компилятор и больше никогда не писать на си

0
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
08.03.2012, 17:48  [ТС] #24
Цитата Сообщение от alex_x_x Посмотреть сообщение
это значит, что адрес одной из переменных поменялся, а если так, то нужно выкинуть компилятор и больше никогда не писать на си
Он скорее всего имел другое. Если взять локальные переменные, то компилятор действительно вправе в одной точке функции держать значение переменной на одном регистре (или участке стека), а в другой точке - на другом регистре (или в другом участке стека), если это не противоречит коду программы. Это же самое может быть и с глобальной переменной. Типа

C
1
2
3
4
5
6
7
8
9
10
11
12
13
int A;
 
void func (void)
{
  ...
  /* В момент работы цикла компилятор в режиме с оптимизациями скорее всего будет
   * держать значение переменной A на регистре, поскольку внутри цикла нет никаких
   * обращений в память по указателям, нет операций вызова, нет каких -либо операций
   * передачи управления под условием и т.п. */
  for (i = 0; i < 10; i++)
    A = A + i;
  ...
}
Скорее всего он что-то прочитал о подобных вещах в книге, но хорошо в суть дела не вник (как и не вник в постановку текущей задачи)
0
alex_x_x
бжни
2455 / 1661 / 134
Регистрация: 14.05.2009
Сообщений: 7,162
08.03.2012, 17:49 #25
Evg, конечно при этом у переменной в регистре отсутствует понятие адреса
начинаю задумываться какие сложности испытывает компилятор при оптимизации кода, однако
0
NoMasters
Псевдослучайный
1909 / 1120 / 90
Регистрация: 13.09.2011
Сообщений: 3,178
08.03.2012, 17:52 #26
Цитата Сообщение от Evg Посмотреть сообщение
но хорошо в суть дела не вник
Скорее не смог толком описать, что хочу сказать
0
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
08.03.2012, 17:58  [ТС] #27
Цитата Сообщение от alex_x_x Посмотреть сообщение
при этом у переменной в регистре отсутствует понятие адреса
Ну это может быть не регистр, а стек. Главное то, что при работе тех кодов, в которых заведомо не будет "всяких прочих" обращений к переменной A, компилятор имеет право построить код, где переменная будет храниться где-то в другом месте. У тебя может быть целая функция, работающая с глобальной переменной, но при этом в начале она один раз прочтётся из памяти на регистр, потом переложится на другой регистр, и в конце с регистра перепишется в память (на своё законное место)

Цитата Сообщение от alex_x_x Посмотреть сообщение
какие сложности испытывает компилятор при оптимизации кода
И какие сложности испытывают те, кто этот код потом читает. Особенно если код компилировался с агрессивными оптимизациями. Особенно если это более качественная архитектура типа IA-64
0
CyBOSSeR
Эксперт С++
2309 / 1682 / 148
Регистрация: 06.03.2009
Сообщений: 3,675
09.03.2012, 20:54 #28
Evg, как уже было сказано выше, компилятор просто удаляет переменную tmp, и в место вызова printf подставляет ее значение. Исправить ситацию можно либо пометив tmp квалификатором volatile, либо так:
C
1
*(&a + (&b - &a)) = 11;
по крайней мере, С++ компилятор на ideone.com стал выдавать ожидаемый ответ после этого.
0
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
09.03.2012, 23:56  [ТС] #29
Цитата Сообщение от CyBOSSeR Посмотреть сообщение
Evg, как уже было сказано выше, компилятор просто удаляет переменную tmp, и в место вызова printf подставляет ее значение
А чему равно её значение? Оно равно "b". Т.е. фактически мы имеем два вызова printf'а, в который подаётся "b". Но они печатают разный результат

Цитата Сообщение от CyBOSSeR Посмотреть сообщение
Исправить ситацию можно либо пометив tmp квалификатором volatil
Из каких соображений мы должны помечать как volatile?

Цитата Сообщение от CyBOSSeR Посмотреть сообщение
либо так
Как я уже говорил, компилятор по каким-то причинам выражение "&a + (&b - &a)" НЕ превращают в "&b", хотя по правилам ассоциативности это следует делать
0
CyBOSSeR
Эксперт С++
2309 / 1682 / 148
Регистрация: 06.03.2009
Сообщений: 3,675
10.03.2012, 12:25 #30
Цитата Сообщение от Evg Посмотреть сообщение
А чему равно её значение? Оно равно "b".
А b в свою очередь инициализируется значением 10 и более никак известным компилятору образом не изменяется. Получается, что в любой момент времени tmp имеет значение 10 и поэтому может быть удалена.
Цитата Сообщение от Evg Посмотреть сообщение
Из каких соображений мы должны помечать как volatile?
В данном случае мы имеем дело с ненужными нам оптимизациями переменной, volatile как раз позволяет явно сказать компилятору, что никакие оптимизации нам не нужны.
Цитата Сообщение от Evg Посмотреть сообщение
Как я уже говорил, компилятор по каким-то причинам выражение "&a + (&b - &a)" НЕ превращают в "&b", хотя по правилам ассоциативности это следует делать
Думаю, это от компилятора зависит. На ideone.com, например, если заменить "&a + 1" на "&a + (&b - &a)", то tmp не удаляется.
0
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
10.03.2012, 12:54  [ТС] #31
Цитата Сообщение от CyBOSSeR Посмотреть сообщение
А b в свою очередь инициализируется значением 10 и более никак известным компилятору образом не изменяется. Получается, что в любой момент времени tmp имеет значение 10 и поэтому может быть удалена
Но это же никак не объясняет, что в двух одинаковых вызовах printf'ов печатаются разные значения

Цитата Сообщение от CyBOSSeR Посмотреть сообщение
В данном случае мы имеем дело с ненужными нам оптимизациями переменной, volatile как раз позволяет явно сказать компилятору, что никакие оптимизации нам не нужны
Тогда снова возвращаемся к примеру из поста #14. Почему там ВСЕГДА будет работать правильно независимо от всяких volatile'ов? Вообще наличие volatile в программе, не работающей с портами ввода-вывода, говорит о том, что ошибка есть либо в программе, либо в компиляторе
0
CyBOSSeR
Эксперт С++
2309 / 1682 / 148
Регистрация: 06.03.2009
Сообщений: 3,675
10.03.2012, 13:54 #32
Цитата Сообщение от Evg Посмотреть сообщение
Но это же никак не объясняет, что в двух одинаковых вызовах printf'ов печатаются разные значения
Почему же? Переменная tmp была удалена и в месте ее чтения было подставлено ее значение - 10, переменная b удалена быть не может, т.к. мы берем ее адрес, вот и получаются у нас два разных значения - одно подставленное компилятором, второе прочитанное из b.
Цитата Сообщение от Evg Посмотреть сообщение
Тогда снова возвращаемся к примеру из поста #14. Почему там ВСЕГДА будет работать правильно независимо от всяких volatile'ов?
Видимо, потому что в этом примере мы явно берем адрес переменной b и затем через него модифицируем значение переменной, поэтому компилятор и не может гарантировать, что переменная tmp в строке "tmp = b;" примет значение 10. По крайней наблюдаемое поведение свидетельствует об этом.
Цитата Сообщение от Evg Посмотреть сообщение
Вообще наличие volatile в программе, не работающей с портами ввода-вывода, говорит о том, что ошибка есть либо в программе, либо в компиляторе
Согласен, но я бы добавлил, что и в многопоточных приложения использование volatile также оправданно.
0
nxnx
Формучанин
362 / 293 / 41
Регистрация: 02.11.2010
Сообщений: 1,234
10.03.2012, 14:24 #33
моё мнение
C
1
2
3
  *(&a + 1) = 11;
 
 tmp = b;
может при оптимизации компилятор сделал так:
C
1
2
3
4
 
tmp = b;
 
 *(&a + 1) = 11;
по сути он ведь не знает что это за адрес (&a + 1)
добавление барьера помогает:
C
1
2
3
4
5
  *(&a + 1) = 11;
 
  asm volatile ("":::"memory"); 
  /* Читаем из переменной "b" */
  tmp = b;
0
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
10.03.2012, 15:16  [ТС] #34
Цитата Сообщение от CyBOSSeR Посмотреть сообщение
Почему же? Переменная tmp была удалена и в месте ее чтения было подставлено ее значение - 10, переменная b удалена быть не может
Начнём с того, что компилятор не имеет права просто так взять и подставить значение глобальной переменной b (и если ты посмотришь в код, то увидишь это). Но на третий круг пояснений заходить уже не хочется. Ты почему-то привязался к переменной tmp. давай перепишем исходник по другому:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
char a = 5;
char b = 10;
 
int main (void)
{
  printf ("&b - &a = %d\n", &b - &a);
 
  *(&a + 1) = 11;
 
  printf ("b = %d\n", b);
  printf ("b = %d\n", b);
  return 0;
}
При запуске на gcc с оптимизациями всё равно в двух абсолютно одинаковых printf'ах будут печататься разные значения.

Цитата Сообщение от CyBOSSeR Посмотреть сообщение
Видимо, потому что в этом примере мы явно берем адрес переменной b
Ну ты же видишь, что в первом файле нет взятия адреса на b. Точно так же ты знаешь, что каждый исходный файл компилятор обрабатывает по отдельности независимо от другого.

nxnx, то, что ты сказал, следует из построенного кода (постфактум). Но почему такое наблюдается в примере из первого поста и НЕ наблюдается в примере из 14-го поста? Ну а добавление барьера - это ровно такая же затычка, как и несколько раз озвученный квалификатор volatile. Т.е. это затычка, которая позволяет обойти ошибку в программе. Но в чём ошибка?
0
nxnx
Формучанин
362 / 293 / 41
Регистрация: 02.11.2010
Сообщений: 1,234
10.03.2012, 15:49 #35
Цитата Сообщение от Evg Посмотреть сообщение
nxnx, то, что ты сказал, следует из построенного кода (постфактум). Но почему такое наблюдается в примере из первого поста и НЕ наблюдается в примере из 14-го поста? Ну а добавление барьера - это ровно такая же затычка, как и несколько раз озвученный квалификатор volatile. Т.е. это затычка, которая позволяет обойти ошибку в программе. Но в чём ошибка?
честно говоря я не читал все посты, в том случае компилятор видимо допускает возможность изменения переменных т.к. он не знает что в функции func(), а в случае с *(&a+1)=11 видимо считает что это просто изменения значения по адресу, а то что адрес совпадает с b - это совпадение, которое не берётся в расчёт, и при оптимизации строки меняются местами поэтому такой результат.

Добавлено через 8 минут
судя по азм коду переменная b сохраняется в регистре eax, затем производится изменение переменной, а затем регистр eax кладется в стек(компилятор не знает что &a+1 есть адрес b), поэтому в первом случае выводится 10, (затем регистр уже загружается из памяти где лежит 11) а потом 11
2
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
10.03.2012, 16:21  [ТС] #36
nxnx, в 16-м посте ответ. Твой ответ, как мне кажется, был самым разумным. Для людей, которые не являются специалистами и не знают об устройстве компилятора изнутри
0
alex_x_x
бжни
2455 / 1661 / 134
Регистрация: 14.05.2009
Сообщений: 7,162
10.03.2012, 16:59 #37
Цитата Сообщение от nxnx Посмотреть сообщение
судя по азм коду переменная b сохраняется в регистре eax, затем производится изменение переменной, а затем регистр eax кладется в стек(компилятор не знает что &a+1 есть адрес b), поэтому в первом случае выводится 10, (затем регистр уже загружается из памяти где лежит 11) а потом 11
Цитата Сообщение от alex_x_x Посмотреть сообщение
собственно компилятор вырезал переменную tmp, положил значение переменной b в eax, после этого изменил значение b, но при выводе tmp использовал старое значение из eax

Не по теме:

просто уже проходили



Добавлено через 23 минуты
а вообще
http://www.cplusplus.com/reference/clibrary/cstddef/ptrdiff_t/
A subtraction of two pointers is only granted to have a valid defined value for pointers to elements of the same array (or for the element just past the last in the array).
For other values, the behavior depends on the system characteristics and compiler implementation.
те компилятор вообще умывает руки при таких операциях с указателями
ессно, если вместо a, b объявит массив arr[2] = { 5, 10 }; то все работает

Добавлено через 11 минут
When two pointers are subtracted, both shall point to elements of the same array object,
or one past the last element of the array object; the result is the difference of the
subscripts of the two array elements. The size of the result is implementation-defined,
and its type (a signed integer type) is ptrdiff_t defined in the <stddef.h> header.
If the result is not representable in an object of that type, the behavior is undefined.
6.5.6.9 стандарта це

хотя может это сильно к делу не относится, но все равно даже с delta получается некорректный код
1
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
10.03.2012, 19:21  [ТС] #38
Цитата Сообщение от alex_x_x Посмотреть сообщение
но все равно даже с delta получается некорректный код
Собственно, до этого места ты докопался и ответ абсолютно правильный. Думаю, отсюда несложно будет экстраполировать пояснение ошибки на первоначальный пример (где нету delta). Ну и до кучи почитать пост #16, где объяснено, к каким последствиям это привело.

Сам допёр?
1
alex_x_x
бжни
2455 / 1661 / 134
Регистрация: 14.05.2009
Сообщений: 7,162
10.03.2012, 19:27 #39
Цитата Сообщение от Evg Посмотреть сообщение
Сам допёр?
помнил, что был такой стремный тип ptrdiff_t, связанный с разностями адресов, а это вычитание как раз не давало покоя, ну а там и было написано
да уж, забавный язык си
0
Evg
Эксперт CАвтор FAQ
19278 / 7135 / 528
Регистрация: 30.03.2009
Сообщений: 19,976
Записей в блоге: 30
10.03.2012, 19:32  [ТС] #40
Потому разработчики языков постоянно и пытаются придумать что-то такое же мощное, как Си, но при этом по возможности лишить программиста работать с указателями (а точнее, с адресной арифметикой) - уж очень много геморроя из этого лезет

Добавлено через 1 минуту
На самом деле в твоей цитате надо было первое предложение жирным выделять
0
10.03.2012, 19:32
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
10.03.2012, 19:32

Указатели и адресная арифметика
Помогите с заданием Язык C. Задание звучит так: 2) Для этого фрагмента...

Указатели, адресная арифметика
Ребята, помогите плиз вот с таким вот заданием... Написать программу,...

Адресная арифметика: поиск max элемента массива
Здравствуйте! Это программа находит максимальный элемент из введеного массива....


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru