Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

static const array gets initialized dynamically in MSVC?

We have a table we'd like to initialize statically, however MSVC (2015.1, and older versions too) generates a dynamic initializer instead.

Here's the simplified code demonstrating the issue:

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

compilation with /FAs /c produces a dynamic initializer for 'Acts' function in the .asm file instead of a nice constant array.

replacing last const by constexpr produces this warning:

t.cpp(54): error C2131: expression did not evaluate to a constant
t.cpp(54): note: a non-constant (sub-)expression was encountered

However I'm not seeing what is non-constant here. Any hints?

like image 499
Igor Skochinsky Avatar asked Mar 16 '16 10:03

Igor Skochinsky


3 Answers

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

I'll assume that's the one you are complaining about. The single-pass compilation model is the larger obstacle here, the compiler cannot make any assumptions about the inheritance model for the parser_t class, it only has the forward declaration to work with. Member function pointers look different depending on whether the class uses single, multiple or virtual inheritance.

You need to help and tell the compiler with the appropriate non-standard extension keyword. Fix:

 class __single_inheritance parser_t;

And the table generation now changes to:

?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

And no dynamic initializer anymore.

like image 136
Hans Passant Avatar answered Nov 14 '22 13:11

Hans Passant


I'm not sure why function pointer address cannot be extracted as a constant - this needs to be asked from Microsoft developers, however - I was able to walk around this problem - if you introduce some virtual function in base class - it then be able to figure out function pointer addresses correctly.

This code does not compile:

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

This code compiles fine:

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

What's the difference - no clue. :-)

like image 39
TarmoPikaro Avatar answered Nov 14 '22 13:11

TarmoPikaro


The issue is that the functions in parser_t are not static, so the compiler does not know what memory address to assign to the elements of Acts[]. If you make the functions static and provide definitions for them, then the compiler can make the associations and set the pointers to the right values. You will probably also need to alter the typedef for action_t (or make a copy).

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,
/*...*/
};

If you want the functions to remain non-static (e.g. parser_t is an abstract class or its functions must be non-static for other reasons), then Acts[] cannot be statically defined and you'll need to instantiate parser_t (or its fully-defined child class) and then assign the elements of Acts[] to the functions via that object.

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

like image 1
Jed Schaaf Avatar answered Nov 14 '22 13:11

Jed Schaaf