"variableName;"这个C++语句在任何时候都是一个空操作吗?

21

在C++中有时会定义一个变量,但是没有使用。这里有一个例子-用于COM_INTERFACE_ENTRY_FUNC_BLIND ATL宏的函数:

HRESULT WINAPI blindQuery( void* /*currentObject*/, REFIID iid, void** ppv, DWORD_PTR /*param*/ ) 
{
    DEBUG_LOG( __FUNCTION__ ); //DEBUG_LOG macro expands to an empty string in non-debug
    DEBUG_LOG( iid );
    iid; // <<<<<<<----silence compiler warning
    if( ppv == 0 ) {
        return E_POINTER;
    }
    *ppv = 0;
    return E_NOINTERFACE;
}
在上面的示例中,iid参数与DEBUG_LOG宏一起使用,该宏在非调试配置中扩展为空字符串。因此,在签名中注释或删除iid变量名称不是一个选项。当编译非调试配置时,编译器会生成一个C4100:'iid':未引用的形式参数警告,因此为了消除警告,被认为是无操作的iid;语句被添加。
问题如下:如果我们有以下任何声明之一:
 CSomeType variableName; //or
 CSomeType& variableName; //or
 CSomeType* variableName;

C++代码中以下语句会如何表现:

variableName;

无论 CSomeType 是什么,始终不执行任何操作?


2
这不会触发“语句没有效果”的警告吗? - falstro
@roe:在Visual C++上不行,可能在其他编译器上可以。 - sharptooth
如果在声明和语句之间滥用预处理器,可能会产生影响。例如 #define variableName exit(-1) - Benoit
@Benoit:那时它不再是一个变量名了,呵呵。 - MSalters
看看选项,你可以将声明包装在宏中。这样就解决了问题"...,REFIID ONLY_DEBUG(iid), ..."。然后在调试模式下,您可以省略参数的名称。 - edA-qa mort-ora-y
显示剩余2条评论
2个回答

52

是的,但你可能会收到另一个警告。

标准的方法是:(void)iid;


从技术上讲,这仍然可能会将iid加载到寄存器中并且什么也不做。尽管编译器会非常愚蠢地这样做(如果它这样做,请删除编译器),但如果要忽略的表达式涉及可观察行为,例如调用IO函数或读写volatile变量,则这是一个更严重的问题。

这带来了一个有趣的问题:我们能否完全忽略一个表达式?

也就是说,现在我们有:

#define USE(x) (void)(x)

// use iid in an expression to get rid of warning, but have no observable effect
USE(iid); 

// hm, result of expression is gone but expression is still evaluated
USE(std::cout << "hmmm" << std::endl);

这是一个接近解决方案的示例:
// sizeof doesn't evaluate the expression
#define USE(x) (void)(sizeof(x))

但是出现了以下错误:

void foo();

// oops, cannot take sizeof void
USE(foo());

解决方案很简单:
// use expression as sub-expression,
// then make type of full expression int, discard result
#define USE(x) (void)(sizeof((x), 0))

这保证了没有操作。

编辑:以上确实保证了没有效果,但我在未测试的情况下发布了。在测试时,它再次生成警告,至少在MSVC 2010中,因为未使用。这不好,需要更多的技巧!


提醒:我们想要“使用”一个表达式而不评估它。怎么做呢?像这样:

#define USE(x) ((void)(true ? 0 : (x)))

这个问题与上次类似,但更糟糕,因为 (x) 需要转换为 int。这个问题其实很简单,可以轻松解决:

#define USE(x) ((void)(true ? 0 : ((x), 0)))

我们回到了上一次的情况(没有效果),但是这一次使用了x,因此不会出现任何警告。完成了,对吗?

实际上,这个解决方案仍然存在一个问题(在上一个未解决的方案中也存在,但未被注意到),并且它在以下示例中出现:

struct foo {};
void operator,(const foo&, int) {}

foo f;
USE(f); // oops, void isn't convertible to int!

也就是说,如果表达式(x)的类型重载了逗号运算符,并且不能转换为int,那么解决方案将失败。虽然这种情况不太可能发生,但为了完全避免出现问题,我们可以采用以下方法进行修复:

#define USE(x) ((void)(true ? 0 : ((x), void(), 0)))

为确保我们最终得到零。这个技巧由 Johannes 带给我们。点击此处
另外,正如所指出的那样,如果上述方法不够,一个愚蠢到足以“加载”表达式0(进入寄存器或其他地方)的编译器可能会忽略它。
我认为这是不可能的,因为我们最终需要一个表达式来产生某种类型来忽略它,但如果我想到了,我会添加的。

1
不,表达式“value”被丢弃了。将该值加载到寄存器中,或以其他方式向CPU指示其存在不会违反该规则。就语言而言,它没有可辨别的副作用。 - edA-qa mort-ora-y
1
我并不是在说一个好的优化器会这样做,也不知道是否有这样的情况发生过,但我想说的是优化器的作者可能犯了一个错误——这种事情时有发生。因此,如果绝对不能生成任何代码,那么最好根本不要存在这段代码。 - edA-qa mort-ora-y
我从GCC得到了警告“逗号左操作数没有效果”,有什么想法吗? - Steven Lu
@GMan 我可能会使用条件语句来检查 MSVC。无论如何,我喜欢坚持使用 gcc。谢谢。 - Steven Lu
1
这个解决方案在 g++ 4.9.2 中会产生警告。 - Zingam
显示剩余12条评论

2

实际上,如果不查看编译器的源代码,就无法百分之百确定。但在现代编译器中,我会非常惊讶,如果它会生成这样的代码。

最终,如果你担心某个特定情况,你可以随时查看生成的汇编代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接