12

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?

CinCout
  • 8,291
  • 9
  • 47
  • 55
Igor Skochinsky
  • 22,978
  • 1
  • 62
  • 101
  • (Works fine in GCC and Clang ([demo](http://melpon.org/wandbox/permlink/08dIKQVNZZMq8zPp)).) – Kerrek SB Mar 16 '16 at 10:41
  • This may be a compiler/platform limitation. The object module format used on MS-Windows may not support an unresolved symbol pointing to that kind of a function. Try initializing a static const array with a pointer to an ordinary function, and see if the compiler generates dynamic initialization code. – Sam Varshavchik Mar 16 '16 at 11:06
  • What if you typedef' the function pointers after `parser_t` is defined. Yes, for this you need to comment out `Table` and `Actions`. Just a thought... – Ajay Mar 16 '16 at 12:24
  • See if the answer changes when compiling with [`/vmv`](https://msdn.microsoft.com/en-us/library/bkb78zf3.aspx). Pointers to members of forward-declared class cause trouble for MSVC. It tries to be clever about the size of such pointers (in violation of the standard), but for that it needs to know the class' layout. `/vmv` restores standard-conforming behavior (at the expense of making all such pointers 16 bytes large). – Igor Tandetnik Mar 16 '16 at 14:16
  • @IgorTandetnik I think you may be onto something, but `/vmv` has no effect, as well as defining the methods before referencing them. I guess we may have to resort to static function wrappers for the methods... – Igor Skochinsky Mar 17 '16 at 08:04
  • @JedSchaaf: if I did, I would get a compilation error, and it compiles fine (try it). – Igor Skochinsky Mar 22 '16 at 15:17
  • Sorry, I deleted my comment because I realized I was thinking backwards about the `typedef`s. I've added an answer that should address your issue. – Jed Schaaf Mar 22 '16 at 16:13

3 Answers3

8
  ??__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.

Hans Passant
  • 873,011
  • 131
  • 1,552
  • 2,371
2

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. :-)

TarmoPikaro
  • 3,568
  • 1
  • 32
  • 42
1

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

Jed Schaaf
  • 940
  • 1
  • 8
  • 19