Подтвердить что ты не робот

Static const array динамически инициализируется в MSVC?

У нас есть таблица, которую мы хотели бы инициализировать статически, однако 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 создает функцию dynamic initializer for 'Acts' в файле .asm вместо хорошего константного массива.

замена последнего const на constexpr вызывает это предупреждение:

t.cpp(54): ошибка C2131: выражение не оценивалось константой
   t.cpp(54): note: было обнаружено непостоянное (суб) выражение

Однако я не вижу здесь непостоянного. Любые подсказки?

4b9b3361

Ответ 1

  [email protected]@YAXXZ PROC     ; `dynamic initializer for 'Acts'', COMDAT

Я предполагаю, что тот, о котором вы жалуетесь. Модель однопроходной компиляции является более крупным препятствием здесь, компилятор не может делать какие-либо предположения о модели наследования для класса parser_t, он имеет только вперед декларацию для работы. Элементы указателя функций выглядят по-разному в зависимости от того, использует ли класс однократное, множественное или виртуальное наследование.

Вам нужно помочь и сообщить компилятору с соответствующим нестандартным ключевым словом расширения. Fix:

 class __single_inheritance parser_t;

И теперь генерация таблицы изменяется на:

[email protected]@[email protected]@AGHXZB DD FLAT:[email protected][email protected]@QAGHXZ ; Acts
    DD  FLAT:[email protected][email protected]@QAGHXZ
    DD  FLAT:[email protected][email protected]@QAGHXZ
    etc...
CONST   ENDS

И никакой динамический инициализатор больше.

Ответ 2

Я не уверен, почему адрес указателя функции не может быть извлечен как константа - это нужно спросить у разработчиков 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,
};

Какая разница - нет подсказки.: -)

Ответ 3

Проблема в том, что функции в 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[] не может быть static и вам нужно создать экземпляр 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: Создание указателя функции для функции члена С++
Объявление указателя: указатели на функции-члены