MSVC中静态常量数组是否动态初始化?

12

我们有一个表需要静态初始化,但是 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 文件中生成 'Acts' 的动态初始化函数,而不是一个漂亮的常量数组。

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

t.cpp(54): error C2131: 表达式未评估为常量
t.cpp(54): note: 非常量 (子) 表达式被遇到

然而我没有看到这里有什么非常量。有什么提示吗?


在GCC和Clang中运行正常(演示)。 - Kerrek SB
这可能是编译器/平台的限制。在MS-Windows上使用的目标模块格式可能不支持指向那种函数的未解决符号。尝试使用指向普通函数的指针初始化静态const数组,并查看编译器是否生成动态初始化代码。 - Sam Varshavchik
如果在定义parser_t之后使用typedef函数指针会怎样呢?是的,为了做到这一点,您需要注释掉TableActions。只是一个想法... - Ajay
当使用/vmv编译时,查看答案是否有所改变。对于前向声明类的成员指针,MSVC会出现问题。它试图聪明地处理这些指针的大小(违反了标准),但为此需要知道类的布局。/vmv恢复了符合标准的行为(代价是使所有这样的指针都变成16字节)。 - Igor Tandetnik
抱歉,我删除了我的评论,因为我意识到我对于typedef的想法是反向的。我已经添加了一个答案,应该可以解决你的问题。 - Jed Schaaf
显示剩余2条评论
3个回答

8
  ??__EActs@@YAXXZ PROC     ; `dynamic initializer for 'Acts'', COMDAT

我会假设您所抱怨的是这个问题。单遍编译模型是更大的障碍,编译器无法对parser_t类的继承模型做出任何假设,它只有前向声明可以使用。成员函数指针在类使用单一、多重或虚拟继承时看起来不同。
您需要使用适当的非标准扩展关键字来帮助并告诉编译器。修复:
 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

不再有动态初始化程序。


2

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

这段代码无法编译:

#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,
};

什么是区别 - 没有头绪。 :-)

1
问题在于parser_t中的函数不是static,因此编译器不知道要分配哪个内存地址给Acts[]的元素。如果将这些函数定义为static并提供定义,那么编译器就能建立关联并将指针设置为正确的值。您可能还需要修改action_ttypedef(或创建一个副本)。
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,
/*...*/
};

如果您希望函数保持非静态(例如,parser_t是一个抽象类或其函数必须是非静态的,出于其他原因),那么Acts[]就不能被静态定义,您需要实例化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: 创建一个指向C++成员函数的函数指针
指针声明:指向成员函数的指针


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