This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

uVision Watch window does not like static ...

I use uVision 4.53 (with Armcc V4.1.0.894), processor STM32F407 with standard settings.

If I compile the following small program:

class Test{
public:
        static int i1;
        static int i2;

        int TestFunc( void);
};

int Test::TestFunc( void){
        i1= i2;
        return 0;
}

Test T;

int main( void){

        T.TestFunc();

        while( 1);
}

Then the watch window will show the class element T with the address "0x20000014 &T". This is a bit strange, as the address of i1 is 0x2000001C and address of i2 is 0x20000018 (as you can easily see if you look at the disassembly of the line "i1=i2" in function TestFunc).

If I try to change the value of T1.i1 in the watch window, it will fail - it always keeps zero, even after I enter some changed value.

The miracle happens, if I change the class definition to

class Test{
public:
        static int i1;
        static int i2;

        static int TestFunc( void);
};

Then the watch window will show the class element T with the address "0x00000000" (this usually would mean, that T is not used in the software - so this evidently is wrong). But nicely now i1 and i2 can be accessed without problems through the watch window.

I came to this problem through large classes with structs. For structs you clearly see the address in the watch window. There I recognized that the adresses sometimes were complete nonsense (it showed 0x4000000 adresses for normal variables - 0x4000000 is reserved for peripheral registers, so it is impossible).

It seems to be important, that all functions which are invoked from some static C-like funktion (as main()), need to be static. Otherwise the watch window does very strange things.

BTW: It would make a bit sense, if in the "Type" field uVision would show "class T" (now it says "struct T", which is slightly disturbing ...).

Parents
  • Ok, I see the problem that is fooling you here as well. 'T' should
    not show up in the symbol window since it is a declarative type but
    not an object. All the members are lifted out to module level leaving
    'T' an orphant/undefined object that should be filtered out by the
    uVision debugger. That is the reason why you get invalid member addresses.

Reply
  • Ok, I see the problem that is fooling you here as well. 'T' should
    not show up in the symbol window since it is a declarative type but
    not an object. All the members are lifted out to module level leaving
    'T' an orphant/undefined object that should be filtered out by the
    uVision debugger. That is the reason why you get invalid member addresses.

Children
  • Thank you for confirmation.

    But why should 'T' not be visible in the watch window? If I want to watch the contents of 'T', it is ultimatively required, that it is visible in the watch window.

    (Please do NOT feel disturbed by the fact, that T contains only static members - the problem persists, if you fill up 'T' with any number of additional non-static members).

    (Further it is really strange, that 'T' works perfectly nice in the watch window, as soon as you define 'TestFunc' as a static func in the class declaration).

  • Well, I see the 'T' case as follows: when all members are static, then
    T is orphant and should be filtered out. Otherwise T should be
    available but the members marked as declaration (those without a location)
    should be removed/hidden from the T structure.

    Given the dwarf type info for 'T', the members i1, i2 and TestFunc
    should be hidden since they are lifted out and therefore have no location
    (that would be some DW_AT_location tag). Showing them when expanding T
    causes the confusion since their locations are undefined:

    000370af 01eef5: 64 = DW_TAG_structure_type
    000370b0 01eef6:   DW_AT_sibling DW_FORM_ref_udata 0x678 (0x1EF38)
    000370b2 01eef8:   DW_AT_name DW_FORM_string 'Test'
    000370b7 01eefd:   DW_AT_byte_size DW_FORM_udata 0x8
    000370b8 01eefe:   38 = DW_TAG_member
    000370b9 01eeff:     DW_AT_name DW_FORM_string 'x1'
    000370bc 01ef02:     DW_AT_type DW_FORM_indirect DW_FORM_ref2 0x89 (0x1E949)
    000370bf 01ef05:     DW_AT_data_member_location DW_FORM_block len=2 DW_OP_plus_uconst 0x0
    000370c2 01ef08:   38 = DW_TAG_member
    000370c3 01ef09:     DW_AT_name DW_FORM_string 'x2'
    000370c6 01ef0c:     DW_AT_type DW_FORM_indirect DW_FORM_ref2 0x89 (0x1E949)
    000370c9 01ef0f:     DW_AT_data_member_location DW_FORM_block len=2 DW_OP_plus_uconst 0x4
    000370cc 01ef12:   42 = DW_TAG_member
    000370cd 01ef13:     DW_AT_name DW_FORM_string 'i1'
    000370d0 01ef16:     DW_AT_type DW_FORM_indirect DW_FORM_ref2 0x89 (0x1E949)
    000370d3 01ef19:     DW_AT_declaration DW_FORM_flag 0x1
    000370d4 01ef1a:     DW_AT_artificial DW_FORM_flag 0x0
    000370d5 01ef1b:   42 = DW_TAG_member
    000370d6 01ef1c:     DW_AT_name DW_FORM_string 'i2'
    000370d9 01ef1f:     DW_AT_type DW_FORM_indirect DW_FORM_ref2 0x89 (0x1E949)
    000370dc 01ef22:     DW_AT_declaration DW_FORM_flag 0x1
    000370dd 01ef23:     DW_AT_artificial DW_FORM_flag 0x0
    000370de 01ef24:   99 = DW_TAG_subprogram
    000370df 01ef25:     DW_AT_sibling DW_FORM_ref_udata 0x677 (0x1EF37)
    000370e1 01ef27:     DW_AT_name DW_FORM_string 'TestFunc'
    000370ea 01ef30:     DW_AT_declaration DW_FORM_flag 0x1
    000370eb 01ef31:     DW_AT_artificial DW_FORM_flag 0x0
    000370ec 01ef32:     DW_AT_type DW_FORM_indirect DW_FORM_ref2 0x89 (0x1E949)
    000370ef 01ef35:     DW_AT_external DW_FORM_flag 0x0
    000370f0 01ef36:     0  null
    000370f1 01ef37:   0  null
    

  • But the problem persists, if I add non-static members to T.

    Even if all is static in T, then it is ultimately necessary that I have some possibility to watch the members of T.

    In no way T should be "filtered out", as it is a used global class.

  • But when will you finally realize that i1 is _not_ (!) a member of T. T is an object. i1 has static declaration so it is not a member of any object. It is a member of the class. The memory location of i1 does not care the slightest about any memory location of any object T, because i1 is basically a completely separate global variable. Just a variable that lives within a separate namespace - the namespace of class Test.

    Step one when programming in C++ is to learn the difference between classes and objects. Any debate about "static" is meaningless until you do grasp that difference.

    A memory dump of the object T will not span the memory use by i1, since i1 isn't part of the memory of the object T. T and i1 are two separate variables in the memory map.

  • Well, if I look at a class in a watch window, I usually expect that the data is presented in some nice form. So I would expect that I see the value of any variable of the class in this watch window.

    Utmost important of course is, that the value of any variable is showing CORRECTLY in the watch window - in the current state of uVision watch window, the variables i1 and i2 are shown INCORRECT (always zero - regardless of the value of i1 and i2).

    If you want to take out static variables from the watch window, you can do this. But then please give me some other EASY possibility to have an easy look at such static variables (It would not be very comfortable if I would have to analyze the memory dump to look at such variables).

  • You do it again.

    "Well, if I look at a class in a watch window [...]"

    You really must separate in your mind between class and object. A class is not an object. And an object isn't a class. And the static keyword means that not all members declared inside a class need to be part of instances of that class. i1 is not part of any instance of class Test. i1 is not accessed through any this pointer. i1 is accessed through addresses computed already at link time, because i1 is a global variable in its own right.

    I have a car. It is an instance of a car class defined by the car manufacturer. But looking at my car, I will not be able to see the number of produced cars of that model - because that is a static information not stored inside my car but stored with the car manufacturer and shared between all instances of this car model.

    If I have a function:

    dump_variable(&variable,sizeof(variable),type_declaration);
    


    That function would only be able to dump the contents that is spanned by the memory region starting at &variable, and sizeof(variable) big.

    So such a function would be able to dump the contents of a Test::i1 variable.

    And such a function would be able to dump the contents of a T variable.

    But a dump of an instance of the Test class (such as your variable T) would not contain the required information to make the function able to dump i1, even if the class description mentions any i1. i1 just isn't inside the memory area spanned by that that T object instance, since i1 is declared static. This is a hard fact. There is nothing open to discussion about this fact. i1 isn't part of T, and i1 may be stored at a higher address, or a lower address. But need not be stored at an address close to T. So knowledge about T does not give knowledge about i1.

    With i1 not part of T - why should a dump of T then dump i1? How would knowledge of T be able to be converted into knowledge about i1?

    The majority of Keil users understands this. For some reason, you don't.

    When 99% of car drivers are running on the wrong side of the road, it might actually be only 1% of the drivers that are on the wrong side...

    You just have to learn to accept that i1 and i2 should conceptually be seen as separate variables, since they just aren't part of T (or any T1, T2, T3, ... you may add to your program). So i1 is dumped by specifically watching i1. i2 is dumped by specifically watching i2. T is dumped by specifically watching T. And i1+i2 aren't part of object T, so T isn't a good way to try to view i1 and i2.

  • I fully agree to Per, but also understand other users
    getting confused by some output showing incorrect values.

    Since the Dwarf debugging information allows remapping of those 'lifted'
    static members back into the class type, this has been addressed so
    that the members are now shown with correct values.

    A new MDK version should become available by around next week.
    Thanks to everybody here pointing this out.

  • I also agree that I don't expect a debugger to show static data members (like 'i1') when viewing an object (like 'T').

    But for completeness, and at the risk of muddying the waters, I want to point out that the C++ language allows accessing static members using an object (or pointer or reference) so, given the declaration earlier in the thread and 'Test *tp;', all three of these are the same:

       ...
       int x1 = Test::i1;
       int x2 = T.i1;
       int x3 = tp->i1;
    

    (The value of the object or pointer is almost certainly ignored at runtime.)