Что не так с этим кодом C
У меня есть фрагмент кода, где я пытаюсь вернуть квадрат значения, на которое указывает *ptr
.
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
main()
{
int a=8,t;
t=square(&a);
printf("%d",t);
}
Его работа прекрасна для меня, но автор этого кода сказал, что это может не сработать по следующей причине:
Поскольку возможно изменение значения *ptr
неожиданно, возможно, что a и b будут отличаться. Следовательно, этот код мог бы вернуть число, которое не является квадратом!. Правильный способ -
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
Я действительно хотел знать, почему он так сказал?
Ответы
Ответ 1
Идея ключевого слова volatile
заключается в том, чтобы точно указать компилятору, что переменная, помеченная как таковая, может неожиданно изменяться во время выполнения программы.
Однако это не делает его источником "случайных чисел" - он просто советует компилятору - то, что отвечает за фактическое изменение содержимого переменной, должно быть другим процессом, потоком, некоторым аппаратным прерыванием - всем, что будет писать в но не встроена в функцию, в которой находит себя волатильная декларация. В "более старые времена" (компиляторы с меньшей магией) все, что он делал, мешало компилятору кэшировать значение переменной в одном из регистров CPU. Я понятия не имею о стратегиях оптимизаций/де-оптимизаций, вызванных его современными компиляторами, но он, по крайней мере, сделает это.
В отсутствии любого такого внешнего фактора "изменчивая" переменная такая же, как и любая другая. Фактически - это точно так же, как и любая другая переменная - поскольку переменные, не помеченные как изменчивые, также могут быть изменены по тем же внешним причинам (но скомпилированный код C не был бы подготовлен для этого в этом случае, что может привести к использованию неправильных значений).
Ответ 2
Поскольку вопрос имеет принятый и правильный ответ, я буду кратким: вот короткая программа, которую вы можете запустить, чтобы увидеть неправильное поведение для себя.
#include <pthread.h>
#include <math.h>
#include <stdio.h>
int square(volatile int *p) {
int a = *p;
int b = *p;
return a*b;
}
volatile int done;
void* call_square(void* ptr) {
int *p = (int*)ptr;
int i = 0;
while (++i != 2000000000) {
int res = square(p);
int root = sqrt(res);
if (root*root != res) {
printf("square() returned %d after %d successful calls\n", res, i);
break;
}
}
done = 1;
}
int main() {
pthread_t thread;
int num = 0, i = 0;
done = 0;
int ret = pthread_create(&thread, NULL, call_square, (void*)&num);
while (!done) {
num = i++;
i %= 100;
}
return 0;
}
Функция main()
порождает поток и изменяет данные, которые квадратизируются в цикле одновременно с другим циклом, вызывающим square
с помощью летучего указателя. Относительно говоря, он не терпит неудачу часто, но делает это очень надежно менее чем за секунду:
square() returned 1353 after 5705 successful calls <<== 1353 = 33*41
square() returned 340 after 314 successful calls <<== 340 = 17*20
square() returned 1023 after 5566 successful calls <<== 1023 = 31*33
Ответ 3
Сначала понять, что изменчиво: Почему волатильность необходима в C?
а затем попытайтесь найти ответ самостоятельно.
Это игра волатильного и аппаратного мира.: -)
Прочитайте ответ, приведенный Chris Jester-Young:
volatile сообщает компилятору, что ваша переменная может быть изменена другими способами, чем код, к которому он обращается. например, это может быть местоположение памяти с отображением ввода-вывода. Если это не указано в таких случаях, некоторые переменные обращения могут быть оптимизированы, например, его содержимое может храниться в регистре, а ячейка памяти не будет снова возвращена.
Ответ 4
Если имеется более одного потока, значение, указываемое указателем, может меняться внутри оператора "a = * ptr" и оператора "b = * ptr". Кроме того: вам нужен квадрат значения, зачем его помещать в две переменные?
Ответ 5
В представленном вами коде нет переменной для переменной a
, которая определена в вашем main
, которая будет изменена, пока выполняется square
.
Однако рассмотрите многопоточную программу. Предположим, что другой поток изменил значение, на которое ссылается ваш указатель. Предположим, что эта модификация произошла после того, как вы назначили a
, но до того, как вы назначили b
, в функции sqaure
.
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
//the other thread writes to *ptr now
b = *ptr;
return a * b;
}
В этом случае a
и b
будут иметь разные значения.
Ответ 6
Автор прав (если * ptr будет изменен другими потоками)
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
//between this two assignments *ptr can change. So it is dangerous to do so. His way is safer
b = *ptr;
return a * b;
}
Ответ 7
Поскольку значение указателя * ptr может меняться между первой привязкой и второй.
Ответ 8
Я не думаю, что значение * ptr может измениться в этом коде, запрещая чрезвычайно необычную (и нестандартную) среду выполнения.
Мы рассматриваем все main()
здесь и не запускаем другие потоки. Переменная a
, адрес которой мы берем, является локальной в main()
, а main()
не сообщает никакой другой функции этого адреса переменной.
Если вы добавили строку mysterious_external_function(&a);
до строки t=square(&a)
, тогда да, mysterious_external_function
может запустить поток и изменить асинхронную переменную a
. Но нет такой строки, так как записанный square()
всегда возвращает квадрат.
(Кстати, был ли OP сообщение тролля?)
Ответ 9
Я вижу, что некоторые ответы с * ptr могут быть изменены другими потоками. Но это не может произойти, поскольку * ptr не является статической переменной данных. Его переменная параметра, а локальная и переменная параметров хранятся внутри стека. Каждый поток имеет свою собственную секцию стека, и если * ptr был изменен другим потоком, он не должен влиять на текущий поток.
Одна из причин, по которой результат может не дать квадрат, может быть прерыванием HW, возможно, до назначения b = * ptr; как указано ниже:
int square(volatile int *ptr) {
int a,b;
a = *ptr; //assuming a is being kept inside CPU registers.
//an HW interrupt might occur here and change the value inside the register which keeps the value of integer "a"
b = *ptr;
return a * b;
}