Что не так с этим кодом C - PullRequest
12 голосов
/ 16 января 2012

У меня есть фрагмент кода, в котором я пытаюсь вернуть квадрат значения, на которое указывает *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 ]

10 голосов
/ 16 января 2012

Идея ключевого слова volatile заключается именно в том, чтобы указать компилятору, что переменная, помеченная как таковая, может неожиданно измениться во время выполнения программы.

Однако это не делает его источником«случайные числа» - это просто советует компилятору - то, что отвечает за фактическое изменение содержимого переменной, должно быть другим процессом, потоком, некоторым аппаратным прерыванием - всем, что записывает в память процесса, но не встраивается в функцию, где находит изменяемое объявлениесам.В «старые времена» (компиляторы с меньшим количеством магии) все, что он делал, не давало компилятору кэшировать значение переменной в одном из регистров ЦП.Я понятия не имею о стратегиях оптимизации / де-оптимизации, запускаемых им современными компиляторами - но он по крайней мере сделает это.

В отсутствие любого такого внешнего фактора переменная «volatile» так же, каклюбой другой.На самом деле - это так же, как и любая другая переменная - поскольку переменные, не помеченные как volatile, также могут быть изменены по тем же внешним причинам (но скомпилированный код C не будет подготовлен для этого в этом случае, что может привести к использованию неправильных значений).

8 голосов
/ 16 января 2012

Поскольку на вопрос есть принятый и правильный ответ, я буду краток: вот короткая программа, которую вы можете запустить, чтобы увидеть, как неправильное поведение происходит для вас.

#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
5 голосов
/ 16 января 2012

Сначала поймите, что изменчиво: Зачем нужно летучее в C?

и затем попытайтесь найти ответ самостоятельно.

Это игра изменчивого и аппаратного мира. : -)

Прочитать ответ, данный Крис Джестер-Янг :

volatile сообщает компилятору, что ваша переменная может быть изменена другими способами, а не кодом, который обращается к ней. например, это может быть ячейка памяти с отображением ввода / вывода. Если это не указано в таких случаях, некоторые переменные доступы могут быть оптимизированы, например, их содержимое может храниться в регистре, и ячейка памяти не будет считываться снова.

2 голосов
/ 16 января 2012

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

2 голосов
/ 16 января 2012

Если существует более одного потока, значение, на которое указывает указатель, может измениться между оператором "a = * ptr" и оператором "b = * ptr".Кроме того: вам нужен квадрат значения, зачем помещать его в две переменные?

1 голос
/ 16 января 2012

Автор правильный (если * 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;
}
0 голосов
/ 27 сентября 2015

Я вижу, что некоторые ответы с * 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;
}
0 голосов
/ 17 января 2012

Я не думаю, что значение * ptr может измениться в этом коде, за исключением крайне необычной (и не соответствующей стандартам) среды выполнения.

Мы смотрим на все main() здесь и не запускаем другие темы. Переменная a, адрес которой мы берем, является локальной в main(), а main() не сообщает какой-либо другой функции адреса этой переменной.

Если вы добавили строку mysterious_external_function(&a); перед строкой t=square(&a), тогда да, mysterious_external_function может запустить поток и асинхронно перемешать переменную a. Но такой строки нет, поэтому написанное square() всегда возвращает квадрат.

(Кстати, был ли ТП постом тролля?)

0 голосов
/ 16 января 2012

Поскольку значение указателя * ptr может изменяться между первой привязанностью и второй.

...