【问题标题】:static const array gets initialized dynamically in MSVC?静态常量数组在 MSVC 中动态初始化?
【发布时间】:2016-07-02 04:13:45
【问题描述】:

我们想要静态初始化一个表,但是 MSVC(2015.1,以及更早的版本) 会生成一个动态初始化器。

这是演示问题的简化代码:

#define idaapi __stdcall
#define MAXSTR 1024
typedef int error_t;
typedef unsigned char uchar;


struct psymbol_t
{
  short what;           /* -1 - is error,                       */
                        /* 0 - any symbol,don't skip it         */
                        /* else lxtype_t                        */
  short callNumber;     /* Number in table of metasymbols       */
                        /* -1 - no metasymbol                   */
                        /* Error code if what == -1             */
  uchar  nextNumber;    /* Number in current table              */
                        /* 0xFF - end                           */
  uchar  actNumber;     /* Number in Actions table              */
                        /* 0xFF - no action                     */
};

class parser_t;
typedef error_t (idaapi parser_t::*action_t)(void);
typedef error_t (idaapi parser_t::*nexttoken_t)(void);

struct token_t
{
  int type;          ///< see \ref lx_
  char str[MAXSTR];     ///< idents & strings
};

class basic_parser_t
{
  nexttoken_t gettok;
  const psymbol_t *const *Table;
  const action_t *Actions;
  bool got_token;
public:
  token_t ahead;
  //bool exported_parse(int goal) { return basic_parser_parse(this, goal); }
};


class parser_t: public basic_parser_t {
public:
/*  0 */ error_t idaapi aArrayStart(void);
/*  1 */ error_t idaapi aComplexEnd(void);
/*  2 */ error_t idaapi aObjectStart(void);
/*  3 */ error_t idaapi aObjectKvpNew(void);
/*  4 */ error_t idaapi aObjectKvpKey(void);
/*  5 */ error_t idaapi aConstant(void);
};


static const action_t Acts[] =
{
/*  0 */ &parser_t::aArrayStart,
/*  1 */ &parser_t::aComplexEnd,
/*  2 */ &parser_t::aObjectStart,
/*  3 */ &parser_t::aObjectKvpNew,
/*  4 */ &parser_t::aObjectKvpKey,
/*  5 */ &parser_t::aConstant
};

使用 /FAs /c 编译会在 .asm 文件中生成一个 dynamic initializer for 'Acts' 函数,而不是一个不错的常量数组。

将最后一个 const 替换为 constexpr 会产生以下警告:

t.cpp(54):错误 C2131:表达式未计算为常量
t.cpp(54):注意:遇到了一个非常量(子)表达式

但是我没有看到这里有什么是非常量的。有什么提示吗?

【问题讨论】:

  • (在 GCC 和 Clang (demo) 中工作正常。)
  • 这可能是编译器/平台限制。 MS-Windows 上使用的目标模块格式可能不支持指向该类型函数的未解析符号。尝试使用指向普通函数的指针初始化静态 const 数组,并查看编译器是否生成动态初始化代码。
  • 如果你 typedef' 定义了parser_t 之后的函数指针会怎样。是的,为此您需要注释掉TableActions。只是一个想法......
  • 查看使用/vmv编译时答案是否改变。指向前向声明类成员的指针会给 MSVC 带来麻烦。它试图巧妙地处理此类指针的大小(违反标准),但为此它需要知道类的布局。 /vmv 恢复符合标准的行为(以使所有此类指针变大 16 字节为代价)。
  • @IgorTandetnik 我认为您可能正在做某事,但/vmv 没有效果,以及在引用它们之前定义方法。我想我们可能不得不求助于方法的静态函数包装器......

标签: c++ arrays visual-c++ static-initialization


【解决方案1】:

我不确定为什么不能将函数指针地址提取为常量 - 但是,这需要向 Microsoft 开发人员询问 - 我能够解决这个问题 - 如果你在基类中引入一些虚函数 - 它然后能够正确计算出函数指针地址。

此代码无法编译:

#include <stdio.h>          // printf

class CBase
{
public:
    void func1()
    {
    }
};

class Test: public CBase
{
public:
    virtual void func2()
    {
    }

    void DoTest1( char* s )
    {
        printf("DoTest1: %s\r\n", s);
    }

    void DoTest2( char* s )
    {
        printf( "DoTest1: %s\r\n", s );
    }
};

typedef void (Test::*funcaction) ( char* s );

static constexpr funcaction g_funs[] =
{
    &Test::DoTest1,
    &Test::DoTest2,
};

这段代码编译得很好:

#include <stdio.h>          // printf

class CBase
{
public:
    virtual void func1()
    {
    }
};

class Test: public CBase
{
public:
    virtual void func2()
    {
    }

    void DoTest1( char* s )
    {
        printf("DoTest1: %s\r\n", s);
    }

    void DoTest2( char* s )
    {
        printf( "DoTest1: %s\r\n", s );
    }
};

typedef void (Test::*funcaction) ( char* s );

static constexpr funcaction g_funs[] =
{
    &Test::DoTest1,
    &Test::DoTest2,
};

有什么区别 - 不知道。 :-)

【讨论】:

    【解决方案2】:

    问题是parser_t 中的函数不是static,所以编译器不知道分配给Acts[] 的元素的内存地址是什么。如果您创建函数static 并为它们提供定义,那么编译器可以建立关联并将指针设置为正确的值。您可能还需要将typedef 更改为action_t(或制作副本)。

    typedef error_t (idaapi *action_t)(void);
    
    class parser_t: public basic_parser_t {
    public:
    /*  0 */ static error_t idaapi aArrayStart(void) { /*...*/ }
    /*...*/
    };
    
    static const action_t Acts[] =
    {
    /*  0 */ &parser_t::aArrayStart,
    /*...*/
    };
    

    如果您希望函数保持非static(例如parser_t 是一个抽象类,或者由于其他原因它的函数必须是非static),那么Acts[] 不能被statically 定义您需要实例化parser_t(或其完全定义的子类),然后通过该对象将Acts[] 的元素分配给函数。

    class parser_t: public basic_parser_t {
    public:
    /*  0 */ error_t idaapi aArrayStart(void) { /* defined here or in child class */ }
    /*...*/
    };
    
    parser_t parser = new parser_t();
    
    const action_t Acts[] =
    {
    /*  0 */ &parser::aArrayStart,
    /*...*/
    };
    

    INFO: Creating a Function Pointer to a C++ Member Function
    Pointer declaration: Pointers to member functions

    【讨论】:

      【解决方案3】:
        ??__EActs@@YAXXZ PROC     ; `dynamic initializer for 'Acts'', COMDAT
      

      我认为这就是您要抱怨的那个。单遍编译模型是这里更大的障碍,编译器不能对parser_t 类的继承模型做任何假设,它只有前向声明可以使用。根据类是使用单继承、多继承还是虚继承,成员函数指针看起来会有所不同。

      您需要帮助并使用适当的非标准extension keyword 告诉编译器。修复:

       class __single_inheritance parser_t;
      

      表格生成现在更改为:

      ?Acts@@3QBQ8parser_t@@AGHXZB DD FLAT:?aArrayStart@parser_t@@QAGHXZ ; Acts
          DD  FLAT:?aComplexEnd@parser_t@@QAGHXZ
          DD  FLAT:?aObjectStart@parser_t@@QAGHXZ
          etc...
      CONST   ENDS
      

      并且不再有动态初始化器。

      【讨论】:

        猜你喜欢
        • 2016-10-28
        • 2011-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-11
        • 1970-01-01
        • 2015-02-18
        • 2011-05-07
        相关资源
        最近更新 更多