这个C代码有什么问题

问题描述:

我有一段代码,我试图返回*ptr指向的值的平方。这个C代码有什么问题

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; 
} 

我真的很想知道他为什么这么说?

+0

如果'* ptr'在赋值给a和赋值给b之间改变,结果不是一个正方形。我不知道什么会导致'* ptr'改变。 – Sjoerd 2012-01-16 12:42:15

+2

在多线程环境中,我能想到的只有* ptr的内容可以在另一个线程中更改。在这种情况下,a可能与b有不同的值。 – Totonga 2012-01-16 12:44:50

+1

你的第二个版本有误导性的签名; 'a * a'是一个'int',隐式转换为'long'对于提高返回值的精度来说太迟了。为了解决这个问题,你应该把'a'声明为'long'。 (当然,在很多系统中,long和int都是同义词。) – ruakh 2012-01-16 14:25:51

volatile关键字的想法是完全以指示变量标注,可以以意想不到的方式改变编译器在程序执行期间。

但是,这并不会使它成为“随机数”的来源 - 它只是建议编译器 - 实际更改变量内容的责任应该是另一个进程,线程,某个硬件中断 - 任何会写入进程内存,但不在易失性声明发现自己的函数中内联。在“较旧的时代”(编译器不太神奇),它所做的一切都是阻止编译器将一个CPU寄存器中的变量值缓存起来。我不知道现代编译器引发的优化/去优化策略 - 但至少会这样做。

在没有任何这样的外部因素的情况下,“易变”变量就像任何其他变量一样。实际上 - 就像任何其他变量一样 - 因为没有标记为volatile的变量也可以通过相同的外部原因来改变(但是在这种情况下编译的C代码不会被准备好,这可能导致使用不正确的值) 。

+2

与线程保持一致。使用'volatile'变量进行线程同步是一个** bug **,它会在某一天得到帮助。参见[N2016](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html)讨论为什么'volatile'没有获取C语言中线程同步的语义++ 11。 – 2012-01-16 18:14:56

+0

'volatile'可以成为线程安全解决方案的一部分。即使是重要的一部分。但它需要真正的专业知识来确切知道如何,何时和在哪里。编写同步原语的人有这方面的专业知识,而我们普通的程序员应该只使用他们的工作。 – ugoren 2012-01-16 18:44:49

如果存在多个线程,则指针指向的值可能会在语句“a = * ptr”和语句“b = * ptr”之间改变。另外:你想要一个值的平方,为什么把它分成两个变量?

先了解什么是挥发性:Why is volatile needed in C?

,然后尝试着自己找到答案。

这是一个易变和硬件世界的游戏。通过克里斯小丑,杨给出 :-)

阅读答案:

挥发性告诉你的变量可以通过其他方式来改变,比正在访问它的代码编译器。例如它可以是I/O映射的存储器位置。如果在这种情况下没有指定它,则可以优化一些变量访问,例如,其内容可以保存在寄存器中,并且内存位置不会再次读回。

+1

downvote的原因? – Azodious 2012-01-16 12:47:02

+0

确实是“downvote的原因?” (/我upvoted) - 这是这里几个实际解决问题的答案之一 - 使用“volatile”关键字。 – jsbueno 2012-01-16 12:50:06

在你目前的代码,那么有没有办法,在您main定义,同时square运行被修改为变量a

但是,考虑一个多线程程序。假设另一个线程修改了你的指针引用的值。并且假设此修改发生在您分配了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; 
} 

在这种情况下,ab将有不同的值。

因为指针* ptr的值可能会在第一个情感和第二个情感之间改变。

作者是正确的(如果* 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; 
} 

由于问题具有可接受的正确答案,因此我将简要介绍一下:这里是一个简短的程序,您可以运行该程序查看自己发生的不正确行为。

#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 
+0

即使在gcc中启用了优化“-O1”,'魔术'也会消失。应该使用'volatile'关键字来防止“关键”变量的优化。在上面的例子中有两个这样的变量:'square'中的'int * p'强制解除引用'p'两次,并且'main()'中的'p'' int num'也必须是'volatile'。 – 2015-09-27 22:10:09

我不认为* PTR的值可以在此代码除非出现极不寻常的改变(与非符合标准的)运行时环境。

我们正在查看整个main()这里,它并没有启动其他线程。变量a(我们正在使用的地址)是main()中的本地变量,而main()不通知该变量地址的任何其他函数。

如果您在t=square(&a)行之前添加行mysterious_external_function(&a);,那么,mysterious_external_function可以启动一个线程和异步骗取了a变量。但是没有这样的路线,所以书面square()总是返回一个正方形。

(当时的OP一个巨魔后,顺便?)

我看到一些答案与* PTR可以被其他线程改变。但是这不会发生,因为* ptr不是一个静态数据变量。它的参数变量和局部参数变量被保存在栈中。每个线程都有自己的堆栈段,如果* ptr已被另一个线程改变,它不应该影响当前线程。

为什么结果不能给出正方形的一个原因可能是在分配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

错了。变量'ptr'是本地的。但它指向某个地址。该地址可能指向其他线程堆栈上的一些变量,甚至指向一个静态变量。值'ptr'不能改变,但'ptr'指向的变量不是本地的,它可以改变。所以,'* ptr'可以被破解。 – 2015-09-27 21:27:06