Что не так с этим кодом с
у меня есть кусок кода, где я пытаюсь вернуть площади значение, на которое указывает *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;
}
Я действительно хотел знать, почему он так сказал?
9 ответов
идея volatile
ключевое слово точно указывает компилятору, что переменная, отмеченная как таковая, может изменяться неожиданными способами во время выполнения программы.
однако это не делает его источником "случайных чисел" - он просто советует компилятору-то, что отвечает за фактическое изменение содержимого переменной, должно быть другим процессом, потоком, некоторым аппаратным прерыванием - все, что будет записывать в память процесса, но не встроено в функцию, где летучие декларации сунется. В "старые времена" (компиляторы с меньшей магией) все, что он делал, мешало компилятору кэшировать значение переменной в одном из регистров ЦП. Я понятия не имею о стратегиях оптимизации/деоптимизации, вызванных современными компиляторами, но, по крайней мере, это будет сделано.
в отсутствие любого такого внешнего фактора "Летучая" переменная такая же, как и любая другая. На самом деле-это так же, как и любая другая переменная - как переменные, не отмеченные поскольку volatile также может быть изменен теми же внешними причинами (но скомпилированный код C не будет подготовлен к этому в этом случае, что может привести к неправильным значениям).
поскольку вопрос имеет принятый и правильный ответ, я буду краток: вот короткая программа, которую вы можете запустить, чтобы увидеть неправильное поведение, происходящее для себя.
#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
сначала поймите, что такое volatile:почему volatile необходим в C?
и потом, попробуйте найти ответ самостоятельно.
Это игра изменчивого и аппаратного мира. :-)
читать ответ Крис Шут-Молодой:
volatile сообщает компилятору, что ваша переменная может быть изменена другими средствами, чем код, который обращается к ней. например, это может быть I / O-сопоставленное местоположение памяти. Если это не в таких случаях может быть оптимизирован доступ к некоторым переменным, например, их содержимое может храниться в регистре, а расположение памяти не считывается.
Если существует более одного потока, значение, на которое указывает указатель, может изменяться между инструкцией " a = * ptr "и инструкцией"b = *ptr". Кроме того: вы хотите квадрат значения, зачем помещать его в две переменные?
в коде, который вы представляете, тогда нет способа для переменной 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
будет иметь разные значения.
автор прав (если *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;
}
Я не думаю, что значение *ptr может измениться в этом коде, исключая чрезвычайно необычную (и не совместимую со стандартами) среду выполнения.
мы смотрим на все main()
здесь, и он не запускает другие потоки. Переменная a
, адрес которого мы берем, является локальным в main()
и main()
не сообщает никакой другой функции адреса этой переменной.
если вы добавили строку mysterious_external_function(&a);
до t=square(&a)
линии, тогда да, mysterious_external_function
может начать нить и diddle a
переменной асинхронно. Но нет такой строки, так как написано square()
всегда возвращает квадрат.
(кстати, ОП был постом троллей?)
Я вижу, что некоторые ответы с *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;
}