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: было обнаружено непостоянное (суб) выражение
Однако я не вижу здесь непостоянного. Любые подсказки?
Ответы
Ответ 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: Создание указателя функции для функции члена С++
Объявление указателя: указатели на функции-члены