在gcc(5.2.0)下使用C++11写opencl的主机端代码时,发现无法像内核代码一样对cl_int2
这样的向量(vector)类型用pos.x,pos.y
这样的别名来访问向量元素,只能用pos.s[0]
这种数组访问的方式。这是为什么?
这本是个小问题,但本人是个完美主义者,总想搞个清楚,最后总算搞清楚了,于是就有了本文。
这是
platform.h
中cl_int2
的定义,可以看出,虽然代码中有,x,y名字定义,但编译开关__CL_HAS_ANON_STRUCT__
导致这部分代码是灰的/无效的
opencl内核代码中向量元素的访问
在opencl内核代码中,对于opencl中的向量类型,既可以使用s0~sF(根据向量长度不同)来访问向量中的指定元素,也可以用元素的别名来访问(x,y,z,w,hi,lo…)
比如向量数据float4
,是由4个float
组成的向量
float4 f;
float s0=f.s0; //f中第一个元素
float s0=f.x; //与前一行等价
float2 f2=f.hi //f中前2个元素组成的float2
可以看出,使用x,y,hi,lo这样的别名,代码更加直观易懂。
opencl主机端向量类型的定义
这些向量类型在主机端都有等价的向量类型定义,区别就是类型名字加了cl_
前缀,如内核代码中int2
类型在主机端是cl_int2
,内核代码中float4
类型在主机端是cl_float4
,
参见下面的cl_float4
的定义:
typedef union
{
cl_float CL_ALIGNED(16) s[4];
#if __CL_HAS_ANON_STRUCT__
__CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; };
__CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; };
__CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; };
#endif
#if defined( __CL_FLOAT2__)
__cl_float2 v2[2];
#endif
#if defined( __CL_FLOAT4__)
__cl_float4 v4;
#endif
}cl_float4;
// 摘自cl_platform.h
从上面cl_float4
的定义可以看出主机端的cl_float4
是个联合体,默认是以数字下标访问向量元素的(s[0],s[1],s[2],s[3])。同时它也支持以别名(x,y,z,w,s0~s3)访问元素。
编译器差异
不过你也看到了这些别名都定义在匿名结构体(anonymous struct)中,而匿名结构体并不是C语言标准的一部分,是编译器自行实现的,所以__CL_HAS_ANON_STRUCT__
宏开关决定编译器是否支持匿名结构体(anonymous struct),控制着是否允许使用别名访问元素。
于是我顺藤摸瓜找到__CL_HAS_ANON_STRUCT__
定义的位置,就是下面这段代码(中文部分是作者加的注释)
/* Define capabilities for anonymous struct members. */
#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ )
// gcc下如果没定义__STRICT_ANSI__,则__CL_HAS_ANON_STRUCT__为1
#define __CL_HAS_ANON_STRUCT__ 1
#define __CL_ANON_STRUCT__ __extension__
#elif defined( _WIN32) && (_MSC_VER >= 1500)
// VS2008以后支持匿名结构体,但会有警告,所以这里会有关闭C4201警告
/* Microsoft Developer Studio 2008 supports anonymous structs, but
* complains by default. */
#define __CL_HAS_ANON_STRUCT__ 1
#define __CL_ANON_STRUCT__
/* Disable warning C4201: nonstandard extension used : nameless
* struct/union */
#pragma warning( push )
#pragma warning( disable : 4201 )
#else
// gcc下如果定义了__STRICT_ANSI__,则__CL_HAS_ANON_STRUCT__为0
#define __CL_HAS_ANON_STRUCT__ 0
#define __CL_ANON_STRUCT__
#endif
// 摘自cl_platform.h
上面这段代码控制了__CL_HAS_ANON_STRUCT__
的定义,可以看出,在使用gcc编译时,__CL_HAS_ANON_STRUCT__
是否为1,取决于是否定义了__STRICT_ANSI__
。
如果定义了__STRICT_ANSI__
,__CL_HAS_ANON_STRUCT__
为0,否则为1。
也就是说,在gcc下编译,如果定义__STRICT_ANSI__
就没办法使用别名访问向量元素。
下图就是我在Eclipse+MinGW(5.2.0)环境下打开cl_platform.h
看到的__CL_HAS_ANON_STRUCT__
的定义,说明__STRICT_ANSI__
被定义了,
根本原因
那么接下来的问题就是:__STRICT_ANSI__
是个什么鬼?
关于__STRICT_ANSI__
来历,请参见我的上一篇博客[《C++11:MinGW当指定-std=c++11选项时 默认定义了__STRICT_ANSI__
》]1
从这篇博客的标题就可以得知,如果编译代码时使用了-ansi
选项,编译器就会定义__STRICT_ANSI__
,我找遍了整个项目代码,确信没有使用过-ansi
(太高端我从来不知道这个选项),所以并不是因为我使用了-ansi
才造成这个问题,而是因为我使用了-std=c++11选
项导致编译器自动定义了__STRICT_ANSI__
。
解决方案
知道了问题的根本原因,解决问题的办法也就有了。
方案1:
第一个办法就是前述博客中最后提到的办法:在使用-std=c++11选项的同时,加上-U__STRICT_ANSI__选项, 用于去掉__STRICT_ANSI__
定义
如果你是用cmake来编译项目代码,可以在CMakeList.txt中加入这样的代码
#判断编译器类型,如果是gcc编译器,则在编译选项中加入c++11支持,并去掉__STRICT_ANSI__定义
if(CMAKE_COMPILER_IS_GNUCXX)
add_compile_options(-std=c++11)
message(STATUS "optional:-std=c++11")
add_compile_options(-U__STRICT_ANSI__)
message(STATUS "optional:-U__STRICT_ANSI__")
endif(CMAKE_COMPILER_IS_GNUCXX)
方案二
修改你的源代码,在#include <CL/cl.hpp>
或#include <CL/opencl.h>
语句之前使用#undef __STRICT_ANSI__
删除__STRICT_ANSI__
定义
#if defined( __GNUC__) && defined( __STRICT_ANSI__ )
#define __STRICT_ANSI__DEFINED__
//删除__STRICT_ANSI__定义
#undef __STRICT_ANSI__
#endif
#include <CL/cl.hpp>
#ifdef __STRICT_ANSI__DEFINED__
#undef __STRICT_ANSI__DEFINED__
//恢复__STRICT_ANSI__定义
#define __STRICT_ANSI__
#endif
代码做上述修改后,重新rebuild index
,
再打开cl_platform.h
看到的__CL_HAS_ANON_STRUCT__
的定义,说明__STRICT_ANSI__
没有被定义,
这时再看cl_int的定义,也正常了
这两种解决方案,你可以根据自己的需要来选择,但第二种方案的没有副作用,不会影响项目中其他部分代码的编译。第一种方案会有潜在的副作用,就是可能会影响项目中与opencl无关的代码的编译。
[1]:http://blog.csdn.net/10km/article/details/51105863