浅谈__declspec(selectany)该何时用

浅谈__declspec(selectany)该何时用

__declspec是一个Microsoft Visual C++特定的编译器属性开关。括号中指明的是哪一个属性生效。关于__declspec的其他属性可以百度“__declspec msdn”查看微软的官方帮助。也可以参看博客中转载的文章。

言归正传。__declspec(selectany)在MSDN中的说明是这样的:

Tells the compiler that the declared global data item (variable or object) is a pick-any 

COMDAT (a packaged function). At link time, if multiple definitions of COMDAT are 

seen, the linker picks one and discards the rest. Selectany can be used in initializing 

global data defined by headers,when the same header appears in more than one 

source file. 

简单翻译一下:告诉编译器定义的全局数据项(变量或对象)这是一套能被任意挑选的COMDAT(一套函数)。在链接时,如果多个COMDAT的定义被找到,链接器将挑选一个并剔除其他的多余的。Selectany可以被用于当定义有初始化全局变量数据的头文件被应用于多于一个的源文件时。

我这样的翻译还是挺虚的,直白了说:当在头文件定义全局变量,并且这个头文件被include多次时可以用这个开关剔除由于多次include而产生的重定义。

看起来这个开关很有用,但这个开关我们用得并不多。很明显,根据msdn的解释,对于定义常量这个开关是用不上的了,而全局变量一般来说定义在cpp中,那么这个开关有什么用呢?

这正是这篇文章要讨论的主题。

(1)基于程序结构的整齐,统一将全局变量定义在一个全部cpp都引用的头文件,这样也就避免了在每个cpp中用extren导入外部变量。当然,这随你的习惯了。

(2)模板的设计。类模板,函数模板,这些肯定不会只被一个cpp所使用,按照一般的习惯都是写在头文件的。但是对于模板,其实现是在编译期完成的,那就要求必须在编译的时候同时找到模板的定义和实现,也就是意味着不可能像以前的习惯一样,将类的声明放在头文件,将实现放在cpp中。那么模板类的静态成员变量怎么办呢?类的静态成员必须在类外部初始化,如果是全写在头文件,当头文件include多于一次的时候就会出现类的静态变量重定义的问题,可以做一个简单的实验:

sy.h:

class A
{
public:
	static int u;
};
int A::u=1;

1.cpp:

#include"sy.h"

int main()
{
	return 0;
}

2.cpp:

#include"sy.h"

就这样三个文件,在一个工程中,组建一下(编译没问题,问题出在链接的时候),就会出现:

2.obj : error LNK2005: "public: static int A::u" (?u@A@@2HA) already defined in sy.obj
Debug/sy.exe : fatal error LNK1169: one or more multiply defined symbols found

这时候就只能使用__declspec(selectany)去解决了,将sy.h的第六行改为:

__declspec(selectany) int A::u=1;

即可解决问题。

下面来讨论一个问题,那么看下面的代码:

key.h:

const char *Function[28]=
{
	"menu","exit","rad",
	"sin","cos","tan",
	"sec","csc","cot",
	"asin","acos","atan",
	"acsc","asec","acot",
	"sh","ch","th","ash",
	"ach","ath","log",
	"lg","ln","sum",
	"mul","A","C"
};

这个头文件被include了多次。我的原意的是希望建立一个字符串的数组常量,上面说了定义常量并不需要用这个开关,但为什么还是会出现重定义的问题呢?而且确实能用__declspec(selectany)去解决。实际上这个可以不用__declspec(selectany)去解决,写成这样即可:

const char* const Function[28]=
{
	"menu","exit","rad",
	"sin","cos","tan",
	"sec","csc","cot",
	"asin","acos","atan",
	"acsc","asec","acot",
	"sh","ch","th","ash",
	"ach","ath","log",
	"lg","ln","sum",
	"mul","A","C"
};

如果在看了我的原意之后并没有发现一开始给的代码有什么问题,请想一下用__declspec(selectany)去解决虽然没有问题,但符合我的原意吗?这样Function的内容到底是变量还是常量?


2022年5月13日补充:

我自己对最后Function例子的理解

这是因为const char *Function[28],实际上应该这样理解:((const char) *)[28],也就是字符串指针指向的内容不可变,指针本身可以指向不同地址,这样指针构成的数组,所以Function还不是真的常量,其实是变量。

如果使用__declspec(selectany)去解决,那就是每个引入这个头文件的cpp都会产生一个Function变量,然后由编译器挑一个删除其他的。

再加上const,就是内容和指针本身都不可变,这时候Function才是真的常量。不会出现重定义的问题了。至于有朋友提到的常量在cpp里面会不会重复占内存空间呢,这个要看编译器(其实应该是链接器)的优化,通常有剔除重复定义的优化,与编译时的重复定义不同,在链接的时候的重复定义如果内容完全一样,链接器是可以剔除重复的而不算错误的。

关于模板的静态数据成员可以放在类内定义并初始化

注意可以放在类定义里面的静态数据成员,仅限于非volatile、非内联、类型是整数或者枚举的、带const修饰的静态数据成员。比如测试一下:

template<class T>
struct XXX
{
    static const void* yyy = nullptr;
};

我在VS2019开C++标准到20都编译不过,报错error C2864: XXX<T>::yyy: 带有类内初始化表达式的静态数据成员必须具有不可变的常量整型类型,或必须被指定为“内联”。

指定为内联肯定不是我们的意愿,内联变量(C++17)实际上是每个用到的地方都是一个独立的变量,就不是我们想要的。

查看C++20标准,其中对静态数据成员的描述如下:

在第十一篇类,第四章类成员,第九节静态成员,第三小节静态数据成员,第4点。只有非volatile、非内联、类型是整数或者枚举的、带const修饰的静态数据成员,在类的定义里声明的时可以指定常量赋值语句作为大括号或者等号初始化器的初始化从句。其他静态数据成员不能指定大括号或者等号初始化器。

可能是自己翻译得不好,不过标准本身也有点迷,只是说明了满足上述条件的静态成员变量,可以在“声明”的时候指定初始化器,然后就不说要怎么“定义”了,也没有明确指出满足上述条件的静态数据成员在初始化的同时就是“定义”

 第十三篇模板,第七章模板声明,第二节类模板,第五小节类模板的静态数据成员。这一节里面没有说定义相关的东西。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值