【问题标题】:volatile variables as argument to functionvolatile 变量作为函数的参数
【发布时间】:2011-03-19 05:37:54
【问题描述】:

拥有此代码:

typedef volatile int COUNT;       

COUNT functionOne( COUNT *number );

int  functionTwo( int *number );

我无法摆脱一些警告..

我在 functionOne 原型中收到此警告 1

[警告] 类型限定符被忽略 函数返回类型

我得到这个警告 2,无论我用 COUNT pointer 参数而不是 int 指针调用 functionTwo 的地方

[警告] cast 丢弃限定符 从指针目标类型

显然变量/指针不能“强制转换”为易失性/非易失性。但每个参数也必须指定为易失性?那么如果已经为非易失性变量定义了任何库函数,我该如何使用它呢?

编辑:使用gcc -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wcast-qual -Wextra -Wstrict-prototypes -Wmissing-prototypes …

编辑:根据 Jukka Suomela 的建议,这是警告二的代码示例

typedef volatile int COUNT;       

static int functionTwo(int *number) {
    return *number + 1;
}

int main(void) {
    COUNT count= 10;
    count = functionTwo(&count);
    return 0;
}

【问题讨论】:

  • 我希望这是一个学术问题。如果这是您要解决的真正问题,那么您选择的方法可能会让您走上一条艰难的道路。我想不出为什么要按值将 volatile 传递给函数的充分理由。
  • @Amardeep 我也找不到任何理由,但如果我不这样做,我会收到第二个警告,告诉我应该在原型中为 volatile 变量指定 volatile

标签: c pointers arguments warnings volatile


【解决方案1】:

volatile 关键字旨在应用于表示存储的对象而不是函数。从函数返回 volatile int 没有多大意义。函数的返回值不会被优化掉(内联函数可能例外,但这完全是另一种情况......),并且没有外部参与者会修改它。当函数返回时,它会将返回值的副本传递给调用函数。 volatile 对象的副本本身不是volatile。因此,尝试返回volatile int 将导致复制,将其转换为非易失性int,这是触发编译器消息的原因。返回 volatile int* 可能有用,但不是 volatile int

将对象按值传递给函数会生成对象的副本,因此使用volatile int 作为函数参数必然涉及忽略限定符的转换。通过地址传递 volatile 是完全合理的,但不是通过值。

根据 C 规范,volatile 的行为完全依赖于实现,因此是 YMMV。

您是否以这种方式使用volatile 来试图破坏某种编译器优化?如果是这样,可能有更好的方法来做到这一点。

编辑: 考虑到您的问题的更新,您似乎可以以不同的方式解决这个问题。如果您试图阻止编译器优化,为什么不采取直接的方法并简单地告诉编译器不要优化某些东西呢?您可以使用#pragma GCC optimize__attribute__((optimize)) 为函数提供特定的优化参数。例如,__attribute__((optimize(0))) 应该禁用给定函数的所有优化。这样,您可以使您的数据类型保持非易失性并避免您遇到的类型问题。如果禁用所有优化有点过多,您还可以使用该属性/pragma 打开或关闭单个优化选项。

编辑: 我能够编译以下代码而没有任何警告或错误:

static int functionTwo(int *number) {
    return *number + 1;
}

typedef union {
                int i;
    volatile    int v;
} fancy_int;

int main(void) {
    fancy_int count;
    count.v = 10;
    count.v = functionTwo(&count.i);
    return 0;
}

这个hack“技术”可能有一些奇怪的副作用,所以在生产使用之前彻底测试它。这很可能与直接将地址转换为 (int*) 没有什么不同,但它不会触发任何警告。

【讨论】:

  • +1 这是一个选项,我将尝试通过优化来解决它,按照 Jukka Suomela 的回答,需要制作对象的副本,但我认为我应该足够了,因为确保 gcc 制造商无论如何都有理由发出警告
【解决方案2】:

我可能在这里偏离了基础,但 volatile 通常与堆栈内存区域无关。因此我不确定下面的原型是否真的很有意义。

volatile int functionOne(volatile int number);

我不确定返回的整数是如何变化的。什么会导致 EAX 的值发生变化?这同样适用于整数。一旦值被压入堆栈以便它可以作为参数传递,什么会改变它的值?

【讨论】:

  • 我认为什么都不会!,但是如果我定义了 functionTwo 之类的函数(没有 volatile 内部参数),如果原型中没有指定 volatile 变量,我每次调用时都会收到warningTwo ..
  • 据我了解,这不是是否可以更改堆栈变量的问题。将堆栈变量声明为volatile 会强制编译器始终从堆栈位置读取/写入值,而不是通过将该堆栈位置的内容缓存在寄存器中来优化代码。
  • 我认为如果functionOne调用setjmp,修改number的值,然后调用一些调用longjmp的代码将控制权返回给setjmp,改变如果声明为volatile,则number 的值将保证在longjmp 中存在,但如果不是,则不能保证。
【解决方案3】:

我不明白您为什么要在函数返回类型上使用 volatile 限定符。分配函数返回值的变量应改为volatile

尝试进行以下更改:

typedef int COUNT_TYPE;
typedef volatile COUNT_TYPE COUNT;       

COUNT_TYPE functionOne( COUNT number );

COUNT_TYPE functionTwo( COUNT_TYPE number );

并且在调用functionTwo() 时,显式转换参数:

functionTwo( (COUNT_TYPE)arg );

HTH, 阿什。

【讨论】:

  • 没错,右值不能是易变的。易失性只能应用于某种形式的存储(即将被放入寄存器的变量)。
  • +1 @Gianni,右值既不能是 const 也不能是 volatile。如果可以的话,这甚至意味着是什么意思?
  • 适用于警告 1,现在看一下警告 2 的问题,参数是指针
  • 我在 Visual Studio 2008 上尝试了您编辑的示例;它抛出一个错误,说从COUNT *int * 的转换是不可能的(我将代码编译为C++,而不是C)。我能够通过 2 种方式进行修复:1 - 将 functionTwo() 定义更改为 int functionTwo( COUNT *number ) 2 - 显式转换参数(正如我在原始帖子中列出的那样)count = functionTwo( (int *)&number );
  • @Praetorian 它是带有这些标志的 gcc(C 编译器),调用 functionTwo( (int *) &number) 给出“[Warning] cast 从指针目标类型丢弃限定符”,也许它应该足以使演员表,会不会是 gcc 错误?
【解决方案4】:

如果我编译

typedef volatile int COUNT;       

static int functionTwo(int number) {
    return number + 1;
}

int main(void) {
    COUNT count = 10;
    count = functionTwo(count);
    return 0;
}

使用

gcc -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wcast-qual \
 -Wextra -Wstrict-prototypes -Wmissing-prototypes foo.c

我没有收到任何警告。我尝试了 gcc 4.0、4.2、4.3 和 4.4。您的warningTwo 听起来像是在传递指针,而不是值,这是另一回事...

编辑:

你最新的例子应该这样写;再次,没有警告:

typedef volatile int COUNT;

static int functionTwo(COUNT *number) { return *number + 1; }

int main(void) { COUNT count = 10; count = functionTwo(&count); return 0; }

编辑:

如果你不能改变functionTwo:

typedef volatile int COUNT;

static int functionTwo(int *number) { return *number + 1; }

int main(void) {
    COUNT count= 10;
    int countcopy = count;
    count = functionTwo(&countcopy);
    return 0;
}

请注意,对 volatile 变量的任何访问都是“特殊的”。在带有functionTwo(COUNT *number) 的第一个版本中,functionTwo 知道如何正确访问它。在带有countcopy的第二个版本中,main函数在分配countcopy = copy时知道如何正确访问它。

【讨论】:

  • functionTwo 的参数是“int *number”,而不是“COUNT *number”,改变它然后你会得到警告(想象你正在处理一个已经很常见的库函数,它得到 * int 作为参数),但您的代码的 vars 是易变的(对于 try 和示例 +1)
  • int 没问题,但如果你有一个数组,为了避免警告而复制一份内存效率不高
  • 这不是关于“避免警告”,而是关于正确性。 :) 使用显式副本,您将拥有正确的语义(无论它在您的实现中意味着什么),并且您确切地知道您在哪里对易失性对象进行了读取或写入访问。如果你只是拿一个指针,丢弃“volatile”限定符,并将它传递给一个库函数,那么所有的赌注都没有了(未定义的行为)。
  • 您是对的,您将知道何时读/写易失性对象。所以还有待观察是否有其他方式,因为如果没有其他方式,在数组的情况下,这是一种内存浪费
【解决方案5】:

编写它的人可能想确保所有操作都是原子的,并将所有 int 变量声明为 volatile(它是同步性差的 MT 应用程序吗?),因此代码中的所有 int 都声明为volatile “为了一致性”。

或者也许通过将函数类型声明为 volatile 他们希望停止对纯函数的重复调用的优化?在函数内部增加一个静态变量可以解决它。 但是,尝试猜测他们的初衷,因为这没有任何意义。

【讨论】:

  • volatile 与原子性无关。
  • @Alexandre 你可能错了,如果你做 var = var + 1 并且 var 不是易失性的,那么它可以在中断服务例程(嵌入式)内的操作中间进行更改或一个线程,然后如果编译器没有读取真正的 var 更新值,则可能是 var = var(Updated) + 1 的操作将在 var = var(NoUpdated) + 1 时失去原子性,更新后的值将是丢失
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-13
  • 2013-12-23
  • 2014-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多