Подтвердить что ты не робот

Что не так с этим кодом 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;
}

Я действительно хотел знать, почему он так сказал?

4b9b3361

Ответ 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;
}