当前位置: 首页 > 工具软件 > Enable Copy > 使用案例 >

GCC9.4 memset() clearing an object of type with no trivial copy-assignment [-Werror=class-memaccess]

宗政昱
2023-12-01

编译环境

lm@lm:~$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.4 LTS (Focal Fossa)"
PRETTY_NAME="Ubuntu 20.04.4 LTS"
HOME_URL="https://www.ubuntu.com/"


lm@lm:~$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.1' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-Av3uEd/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)

编译问题

./src/Control.cc:128:62: error: ‘void* memset(void*, int, size_t)’ clearing an object of type ‘Context_Struct’ {aka ‘struct Context_Struct’} with no trivial copy-assignment; use assignment or value-initialization instead [-Werror=class-memaccess]
  128 |     memset(&createInfo, 0, sizeof(Context_Struct));
      |                                                              ^
In file included from /repos/src/Trans.h:5,
                 from ./inter/Control.h:13,
                 from ./inter/Control.cc:1:
./src/inter/interfaceType.h:146:20: note: ‘Context_Struct’ {aka ‘struct Context_Struct’} declared here
  146 |     typedef struct Context_Struct
      |                    ^~~~~~~~~~~~~~~~~~
cc1plus: all warnings being treated as errors

关键源码

部分关键源码如下:

125   void Control::handleCContext()
126   {
127      Context_Struct createInfo;
128      memset(&createInfo, 0, sizeof(Context_Struct));
129      
130      ......
147
148   }

从编译错误提示可以很明显看出是 memset(&createInfo, 0, sizeof(Context_Struct)); 这一行有问题。

寻找问题

关于GCC warning 解决办法可以出门左转查看: GCC 使用指南(gcc 9.4 compiling、warning 解决办法)

这里直接查看 GCC 9.4 官网的编译选项 3.5 Options Controlling C++ Dialect 原文摘录如下:

-Wclass-memaccess (C++ and Objective-C++ only)
 
    Warn when the destination of a call to a raw memory function such as memset or memcpy is an object of class type, and when writing into such an object might bypass the class non-trivial or deleted constructor or copy assignment, violate const-correctness or encapsulation, or corrupt virtual table pointers. Modifying the representation of such objects may violate invariants maintained by member functions of the class. For example, the call to memset below is undefined because it modifies a non-trivial class object and is, therefore, diagnosed. The safe way to either initialize or clear the storage of objects of such types is by using the appropriate constructor or assignment operator, if one is available.
 
      std::string str = “abc”;
      memset (&str, 0, sizeof str);
 
    The -Wclass-memaccess option is enabled by -Wall. Explicitly casting the pointer to the class object to void * or to a type that can be safely accessed by the raw memory function suppresses the warning.

附上中文翻译结果:

-Wclass-memaccess(仅限 C++ 和 Objective-C++)
 
    当调用原始内存函数(如 memset 或 memcpy)的目标是类类型的对象时发出警告,并且当写入此类对象时可能会绕过类非平凡或已删除的构造函数或复制赋值,违反 const 正确性或封装或损坏的虚拟表指针。修改此类对象的表示可能会违反由类的成员函数维护的不变量。例如,下面对 memset 的调用是未定义的,因为它修改了一个非平凡的类对象,因此被诊断出来。初始化或清除此类对象存储的安全方法是使用适当的构造函数或赋值运算符(如果可用)。
 
      std::string str = “abc”;
      memset (&str, 0, sizeof str);
 
    -Wclass-memaccess 选项由 -Wall 启用。将指向类对象的指针显式转换为 void * 或原始内存函数可以安全访问的类型会抑制警告。

解决方案

知道了问题所在,解决问题的方式就比较清楚了。

方案一

最简单粗暴的方式,就是忽略编译告警的方式,即通过在编译选项中添加 -Wno-error=class-memaccess 方式。
具体修改方式为:

  1. 找到引起告警的产品代码源文件所在的 makefile 文件(或者 cmake);
  2. 查看编译选项(一般是makefile文件)xx_xx_CPPFLAGS(x_xx是最终会生成的 lib 库)
  3. 添加或者追加编译选项 xx_xx_CPPFLAGS := -Wno-error=class-memaccess
  4. 重新编译产品代码

当然,使用此种方案,会在编译的 log 中看到下面的 warning 提示信息:

./src/Control.cc: In member function ‘void Control::handleContext(CallData*, const CreateData*, CreatedData*)’:
./inter/Control.cc:128:62: warning: ‘void* memset(void*, int, size_t)’ clearing an object of type ‘Context_Struct’ {aka ‘struct Context_Struct’} with no trivial copy-assignment; use assignment or value-initialization instead [-Wclass-memaccess]
  128 |     memset(&createInfo, 0, sizeof(Context_Struct));
      |                                                              ^
In file included from ./src/inter/Trans.h:5,
                 from ./inter/Control.h:13,
                 from ./inter/Control.cc:1:
./src/inter/interfaceType.h:146:20: note: ‘Context_Struct’ {aka ‘struct Context_Struct’} declared here
  146 |     typedef struct Context_Struct
      |                    ^~~~~~~~~~~~~~~~~~

方案二

GCC 从 4.3 开始就支持 C++11,而我们的编译选项中也添加有 C++11 的编译选项。通过查看 C++11 Support in GCC 中的 Initializer lists 示例我们看到有更加简单的初始化方式:

 int* e {};           // initialization to zero / null pointer
 int** pp {};         // initialized to null pointer
 
 struct S {
    // no initializer-list constructors
    S(int, double, double);       // #2
    S();                          // #3
    // ...
  };
  S s1 = { 1, 2, 3.0 };          // ok: invoke #2
  S s2 { 1.0, 2, 3 };            // error: narrowing
  S s3 { };                      // ok: invoke #3

  struct S2 {
    int m1;
    double m2,m3;
  };
  S2 s21 = { 1, 2, 3.0 };       // ok
  S2 s22 { 1.0, 2, 3 };         // error: narrowing
  S2 s23 {};                    // ok: default to 0,0,0

通过了解上面的示例,我想屏幕前的你应该知道怎么修改代码也是可以实现下面两行代码的功能。

127      Context_Struct createInfo;
128      memset(&createInfo, 0, sizeof(Context_Struct));

最后我们修改后的代码如下:

127      Context_Struct createInfo{};

综上两种解决方案,建议优先考虑方案方案二,其次再考虑方案一。

对象初始值设零

在后期的代码移植中,发现了非声明初始化的情况。即在 *.h 中声明该变量,在对应的 .cc 文件中使用前需要将该对象的初始值设为零。
部分关键代码如下所示:

152   memset(&createInfo_m, 0, sizeof(Context_Struct));
153   createInfo_m.value = 3;
154   createInfo_m.LenCode = l_code;

此时编译错误一般为:

Trans.cc:152:77: error: ‘void* memset(void*, int, size_t)’ clearing an object of type ‘Context_Struct’ {aka ‘struct Context_Struct’} with no trivial copy-assignment; use assignment or value-initialization instead [-Werror=class-memaccess]
 152  |        memset(&createInfo_m, 0, sizeof(Context_Struct));
      |                                                                             ^
In file included from ./src/inter/Trans.cc:1:
./src/interType.h:126:20: note: ‘Context_Struct’ {aka ‘struct Context_Struct’} declared here
  126 |     typedef struct Context_Struct
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~
cc1plus: all warnings being treated as errors

通过查看 cppreference.com 中的 Zero initialization 章节,发现,针对上面的场景,可以使用下面的两种方式来进行对象的初始值设为零(0)操作。

方法一

152   //memset(&createInfo_m, 0, sizeof(Context_Struct));
153   createInfo_m = Context_Struct{};
154   createInfo_m.value = 3;
155   createInfo_m.LenCode = l_code;

方法二

152   //memset(&createInfo_m, 0, sizeof(Context_Struct));
153   createInfo_m = {};
154   createInfo_m.value = 3;
155   createInfo_m.LenCode = l_code;

尝试过方法一和方法二,都是可以了。推荐方法一,显示会更加清晰一些。

 类似资料: