Что не так с этим кодом с

у меня есть кусок кода, где я пытаюсь вернуть площади значение, на которое указывает *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 может изменяться между первой привязанностью и второй.


Я не думаю, что значение *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;
}