Expression Handling

This chapter discusses expression handling, and covers the following topics:

Overview of expression handling

The Watcom Debugger is capable of handling a wide variety of expressions. An expression is a combination of operators and operands selected from application variables and names, debugger variables, and constants. Expressions can be used in a large number of debugger commands and dialogs. For example, the evaluated result of an expression may be displayed by choosing New in the Watches window or by using the Print command.

The appropriate syntax of an expression, that is, the valid sequence of operators and operands, depends on the grammar of the language that's currently established. The Watcom Debugger supports the grammars of the C, C++, and FORTRAN 77 languages.


Note: QNX doesn't support FORTRAN 77. You need to use it in the debugger only if you're remotely debugging an application that's written in FORTRAN 77 on a platform that supports it.

A grammar is selected automatically by the debugger when tracing the execution of modules in an application. For example, part of an application might be written in C, another part in C++, and another part in FORTRAN 77. The modules must have been compiled by one of the WATCOM C, C++ or FORTRAN 77 compilers. When tracing into a module written in one of these languages, the debugger automatically selects the appropriate grammar.

In addition to this automatic selection, a particular grammar may be selected using the debugger Set LAnguage command. The language currently selected can be determined using the SHow Set LAnguage command.

General rules of expression handling

The debugger handles two types of expressions. The difference between the two types of expressions is quite subtle:

  • One is called an expression, and things operate as you would normally expect. This type of expression is used for all ``higher-level'' operations such as adding items to the Watches window.
  • The other type is called an address expression. It's used whenever the debugger prompts for an address and in ``lower-level'' commands such as Examine and Modify.

If the notation for a particular command argument is address, it is an address expression. If the argument is given as expr then it's a normal expression. The difference between the two forms lies in how they treat symbol names:

  • In a normal expression the value of a symbol is its rvalue, or contents.
  • In an address expression, the value of a symbol is (sometimes) its lvalue, or address.

Consider the following case. You have a symbol sam at offset 100, and the word at that location contains the value 15. If you enter sam into the Watches window, you expect the value 15 to be printed, and since the Watches window takes a normal expression, that's what you get.

Now let's try it with the Breakpoint dialog. Enter sam in the address field. The Breakpoint dialog uses the result of its expression as the address at which to set a breakpoint. The Breakpoint dialog takes an address expression, and an implicit unary & operator appears in front of symbols. The debugger has a set of heuristics that it applies to determine whether it should use the rvalue or lvalue of a symbol.

Language-independent variables and constants

Symbol names

Regardless of the programming language used to code the modules of an application, the names of variables and routines are available to the debugger (provided that the appropriate symbolic debugging information has been included with the application's execution module). The debugger doesn't restrict the way in which names are used in expressions. A name could represent a variable, but it could also represent the entry point into a routine.

The syntax of a symbol name reference is quite complicated:

[[[image]@][module]@][routine_name.]symbol_name


Note: If any part of the symbol name reference contains a character that could be interpreted as an operator (for example, a period or hyphen), enclose it in back quotes (`) to force the debugger to interpret it as a symbol name. For example:
    `aaa.bbb`@@ccc

Generally, an application consists of many modules that were compiled separately. The current image is the one containing the module that's currently executing. The current module is the one containing the source lines currently under examination in the Source or Assembly window. By default, the Source window's title line contains the current module name. The current routine is the one containing the source line at which execution is currently paused.

The following are examples of references to symbol names.

  • symbol_name
  • main
  • WinMain
  • FMAIN
  • printf
  • LIB$G_OPEN
  • stdin

If the symbol doesn't exist in the current scope then it must be qualified with its routine name. Generally, these are variables that are local to a particular routine. For example:

  • routine_name.symbol_name
  • main.curr_time
  • main.tyme
  • SUB1.X
  • SUB2.X

If the symbol isn't externally defined and it doesn't exist in the current module, then it may be qualified with its module name. In the C and C++ programming languages, we can define a variable that is global to a module but known only to that module (static storage class). For example:

    static char *NarrowTitle = { "Su Mo Tu We Th Fr Sa" };

In the above example, NarrowTitle is global to the module calendar. If the current module isn't calendar, then the module name can be used to qualify the symbol as shown in the following example.

    calendar@NarrowTitle

If the symbol is local to a routine that's not in the current module, then it must be qualified with its module name and routine name. For example:

    module_name@routine_name.symbol_name
    calendar@main.curr_time
    calendar@main.tyme
    subs@SUB1.X
    subs@SUB2.X

If the symbol is local to an image that's not in the current executable, then it must be fully qualified with the image name. For example:

    prog_name@@routine_name
    prog_name@module_name@routine_name
    prog_name@module_name@routine_name.symbol_name
    dll_name@calendar@main.curr_time
    dll_name@calendar@main.tyme
    program@subs@SUB1.X
    program@subs@SUB2.X

There is a special case for the primary executable image. This is the name of the program you specified when you started the debugger. You can refer to it by omitting the image name. The following examples all refer to symbols in the primary executable image:

    @@WinMain
    @module@WinMain
    @@routine.symbol

In FORTRAN 77, all variables (arguments, local variables, COMMON block variables) are available to the subprogram they're defined or referenced in. The same symbol name can be used in more than one subprogram. If it's a local variable, it represents a different variable in each subprogram. If it's an argument, it might represent a different variable in each subprogram. If it's a variable in a COMMON block, it represents the same variable in each subprogram where the COMMON block is defined. For example:

    SUBROUTINE SUB1( X )
    REAL Y
    COMMON /BLK/ Z
        .
        .
        .
    END
    SUBROUTINE SUB2( X )
    REAL Y
    COMMON /BLK/ Z
        .
        .
        .
    END

In the above example, X is an argument and doesn't need to refer to the same variable in the calling subprogram. For example:

    CALL SUB1( A )
    CALL SUB2( B )

The variable Y is a different variable in each of SUB1() and SUB2(). The COMMON block variable Z refers to the same variable in each of SUB1() and SUB2() (different names for Z could have been used). To refer to X, Y, or Z in the subprogram SUB2(), you would specify SUB2.X, SUB2.Y, or SUB2.Z. If SUB2() is in the module MOD, and MOD isn't the current module, you would specify MOD@SUB2.X, MOD@SUB2.Y, or MOD@SUB2.Z.


Note: Global and local symbol name debugging information is included in an executable image if you request it of the linker. However, local symbol information must be present in your object files. The WATCOM C, C++ and FORTRAN 77 compilers can include local symbol debugging information in object files by specifying the appropriate compiler option. See the chapter Preparing a Program to be Debugged.

Line numbers

Regardless of the programming language used to code the modules of an application, line number information identifying the start of executable statements is available to the debugger (provided the appropriate symbolic debugging information has been included with the application's execution module). The debugger doesn't restrict how line number references are used in expressions. A line number represents the code address of an executable statement in a routine. Not all line numbers represent executable statements; thus some line numbers might not be valid in an expression. For example, source lines consisting of comments don't represent executable statements.

The general format for a line number reference is:

[ [image]@ ] [module_name] @ decimal_digits

The following are examples of references to executable statements:

    @36
    @@45
    @51
    @125
    hello@9
    @hello@9
    prog@hello@9
    otherprg@goodbye@9
    puzzle@50
    calendar@20
    SUB1@30

If the line number doesn't exist in the current module, it must be qualified with its module name. If it doesn't exist in the current image, it must be qualified with the image name. Line numbers aren't necessarily unique. For example, an executable statement could occur at line number 20 in several modules. The module name can always be used to uniquely identify the line 20 we're interested in. In the above examples, we explicitly refer to line 20 in the module calendar. When the module name is omitted, the current module is assumed.


Note: Line number debugging information is included in an executable image only if you compile and link your application with the appropriate options. See the chapter Preparing a Program to be Debugged.

Constants

The debugger supports arithmetic and character constants. Each constant has a data type associated with it. Arithmetic constants consist of those constants whose data type is integer, real, or complex (FORTRAN only). C treats character constants like arithmetic constants so they can be used in arithmetic expressions. FORTRAN treats character constants as constants of type CHARACTER so they can't be used in arithmetic expressions.

Integer constants

An integer constant is formed by a non-empty string of digits preceded by an optional radix specifier. The digits are taken from the set of digits valid for the given radix, if specified, or the current default radix. If the current radix is 10, then the digits are 0 through 9. If the current radix is 16, then the digits are 0 through 9 and A through F or a through f. See the section ``Options Dialog'' in the Debugger Environment chapter.

The following are examples of integer constants:

    123
    57DE
    1423
    345
    34565788

You can define radix specifiers, but two are predefined by the debugger:

0x
may be defined to be a radix specifier for hexadecimal (base 16) numbers.
0n
may be defined to be a radix specifier for decimal (base 10) numbers.

For example:

  0x1234      hexadecimal
  0n1234      decimal
  255         decimal
  0xff        hexadecimal
  0x1ADB      hexadecimal
  0n200       decimal
  0x12fc0     hexadecimal

Real constants

We first define a simple real constant as follows: an optional sign, followed by an integer part, followed by a decimal point, followed by a fractional part. The integer and fractional parts are non-empty strings of digits. The fractional part can be omitted.

A real constant has one of the following forms:

  • simple real constant.
  • simple real constant followed by an E or e, followed by an optionally signed integer constant.

The optionally signed integer constant that follows the E is called the exponent. The value of a real constant that contains an exponent is the value of the constant preceding the E multiplied by the power of ten determined by the exponent.

The following are examples of real constants:

    123.764
    0.4352344
    1423.34E12
    +345.E-4
    -0.4565788E3
    2.E6
    1234.

Note: The accepted forms of floating-point constants are a subset of that supported by the FORTRAN 77 programming language. The debugger doesn't support floating-point constants that begin with a decimal point (for example, .4352344) or have no decimal point (for example, 2E6). However, both forms would be acceptable to a FORTRAN compiler. Also, the debugger doesn't support double-precision floating-point constants where ``D'' is used instead of ``E'' for the exponent part (for example, 2D6, 2.4352344D6). All floating-point constants are stored internally by the debugger in double-precision format.

Complex constants (FORTRAN only)

A complex constant consists of the following parts:

  1. a left parenthesis
  2. a real or integer constant representing the real part of the complex constant
  3. a comma
  4. a real or integer constant representing the imaginary part of the complex constant
  5. a right parenthesis.

The following are examples of complex constants:

    ( 1423.34E12, 3 )
    ( +345, 4 )

Complex constants are accepted when the debugger's currently established language is FORTRAN. The language currently selected can be determined using the SHow Set LAnguage command.

Character constants (C and C++ only)

In the C and C++ programming languages, a character constant consists of an apostrophe followed by a single character followed by an apostrophe. The apostrophes aren't part of the data. An apostrophe in character data represents one character, namely the apostrophe. A character constant must have length 1.

The following are examples of character constants.

    'A'
    'e'
    '''

The C/C++ form of a character constant is accepted when the debugger's currently established language is C or C++. The language currently selected can be determined using the SHow Set LAnguage command.

Character string constants (FORTRAN only)

In the FORTRAN 77 programming language, a character constant consists of an apostrophe followed by any string of characters followed by an apostrophe. The apostrophes aren't part of the data. If an apostrophe is to appear as part of the data, it must be followed immediately by another apostrophe.


Note: Blanks are significant.

The length of the character constant is the number of characters appearing between the delimiting apostrophes. Consecutive apostrophes in a character data represent one character, namely the apostrophe. A character constant must not have length 0.

The following are examples of character constants:

    'ABCDEFG1234567'
    'There''s always tomorrow'

The FORTRAN form of a character constant is accepted only when the debugger's currently established language is FORTRAN.

Memory references

In addition to referring to memory locations by symbolic name or line number, you can also refer to them using a combination of constants, register names, and symbol names. In the Intel 80x86 architecture, a memory reference requires a segment and offset specification. When symbol names are used, these are implicit. The general form of a memory reference is:

[segment:]offset

When an offset is specified alone, the default segment value is taken from the CS, DS or SS register, depending on the circumstances.

Predefined debugger variables

The debugger defines a number of symbols that have special meaning. These symbols are used to refer to the computer's registers and other special variables. For more information, see Appendix B: Predefined Symbols.

General Purpose Registers
eax, ax, al, ah, ebx, bx, bl, bh, ecx, cx, cl, ch, edx, dx, dl, dh
Index Registers
esi, si, edi, di
Base Registers
esp, sp, ebp, bp
Instruction Pointer
eip, ip
Segmentation Registers
cs, ds, es, fs, gs, ss
Flags Registers
fl, fl.o, fl.d, fl.i, fl.s, fl.z, fl.a, fl.p, fl.c, efl, efl.o, efl.d, efl.i, efl.s, efl.z, efl.a, efl.p, efl.c
8087 Registers
st0, st1, st2, st3, st4, st5, st6, st7
8087 Control Word
cw, cw.ic, cw.rc, cw.pc, cw.iem, cw.pm, cw.um, cw.om, cw.zm, cw.dm, cw.im
8087 Status Word
sw, sw.b, sw.c3, sw.st, sw.c2, sw.c1, sw.c0, sw.es, sw.sf, sw.pe, sw.ue, sw.oe, sw.ze, sw.de, sw.ie
Miscellaneous Variables
dbg$32, dbg$bottom, dbg$bp, dbg$code, dbg$cpu, dbg$ctid, dbg$data, dbg$etid, dbg$fpu, dbg$ip, dbg$left, dbg$monitor, dbg$os, dbg$pid, dbg$psp, dbg$radix, dbg$remote, dbg$right, dbg$sp, dbg$top, dbg$nil, dbg$src, dbg$loaded

The debugger permits the manipulation of register contents and special debugger variables (for example, dbg$32) using any of the operators described in this chapter. By default, these predefined names are accessed just like any other variables that you or the application defined.

Should the situation ever arise where the application defines a variable whose name conflicts with that of one of these debugger variables, the module specifier _dbg may be used to resolve the ambiguity. For example, if the application defines a variable called cs then _dbg@cs can be specified to resolve the ambiguity. The _dbg@ prefix indicates that we are referring to a debugger-defined symbol rather than an application-defined symbol.

Register aggregates

Sometimes a value may be stored in more than one register. For example, a 32-bit long integer value may be stored in the register pair DX:AX. We require a mechanism for grouping registers to represent a single quantity for use in expressions.

We define the term register aggregate as any grouping of registers to form a single unit. An aggregate is specified by placing register names in brackets, in order from most significant to least significant. Any aggregate may be specified as long as it forms an 8-, 16-, 32- or 64-bit quantity. The following table gives examples of some of the many aggregates that can be formed:

Size Register aggregate
8 bits[al]
16 bits [ah al]
[bl ah]
[ax]
32 bits [dx ax]
[dh dl ax]
[dh dl ah al]
[ds di]
64 bits [ax bx cx dx]
[edx eax] (386/486/Pentium only)

In some cases, the specified aggregate may be equivalent to a register. For example, the aggregates [ah al] and [ax] are equivalent to ax.

The default type for 8-bit, 16-bit, and 32-bit aggregates is integer. The default type for 64-bit aggregates is double-precision floating-point. To force the debugger into treating a 32-bit aggregate as single-precision floating-point, the type coercion operator [float] may be used.

Operators for the C grammar

The debugger supports most C operators and includes an additional set of operators for convenience. The WATCOM C Language Reference manual describes many of these operators.

The syntax for debugger expressions is similar to that of the C programming language. Operators are presented in order of precedence, from lowest to highest. Operators on the same line have the same priority.

Assignment Operators (lowest priority):

    =  +=  -=  *=  /=  %=  &=  |=  ^=  <<=  >>=

Logical Operators:

    ||
    &&

Bit Operators:

    |
    ^ 
    &

Relational Operators:

    ==  != 
    >   <=  <  >=

Shift Operators:

    <<	>>

Arithmetic Operators:

    +  -
    *  /  %

Unary Operators:

    +  -  ~  !  ++  --  &  *  %
    sizeof unary_expr
    sizeof(type_name)
    (type_name) unary_expr
    [type_name] unary_expr
    ?

Binary Address Operator (highest priority):

    :

Parentheses can be used to order the evaluation of an expression.

In addition to the operators listed above, a number of primary expression operators are supported. These operators are used in identifying the object to be operated upon.

[ ]
subscripting, substringing
()
function call
.
field selection
->
field selection using a pointer

The following sections describe the operators presented above.

Assignment operators

=
Assignment: The value on the right is assigned to the object on the left.
+=
Additive assignment: The value of the object on the left is augmented by the value on the right.
-=
Subtractive assignment: The value of the object on the left is reduced by the value on the right.
*=
Multiplicative assignment: The value of the object on the left is multiplied by the value on the right.
/=
Division assignment: The value of the object on the left is divided by the value on the right.
%=
Modulus assignment: The object on the left is updated with MOD(left,right). The result is the remainder when the value of the object on the left is divided by the value on the right.
&=
Bit-wise AND: The bits in the object on the left are ANDed with the bits of the value on the right.
|=
Bit-wise inclusive OR: The bits in the object on the left are ORed with the bits of the value on the right.
^=
Bit-wise exclusive OR: The bits in the object on the left are exclusively ORed with the bits of the value on the right.
<<=
Left shift: The bits in the object on the left are shifted to the left by the amount of the value on the right.
>>=
Right shift: The bits in the object on the left are shifted to the right by the amount of the value on the right. If the object on the left is described as unsigned, the vacated high-order bits are zeroed. If the object on the left is described as signed, the sign bit is propagated through the vacated high-order bits. The debugger treats registers as unsigned items.

Logical operators

&&
Logical conjunction: The logical AND of the value on the left and the value on the right is produced. If either of the values on the left or right is equal to 0, then the result is 0; otherwise the result is 1.
||
Logical inclusive disjunction: The logical OR of the value on the left and the value on the right is produced. If either of the values on the left or right isn't equal to 0, then the result is 1; otherwise the result is 0.
Note: If the value on the left is non-zero, then the expression on the right isn't evaluated. This is known as short-circuit expression evaluation.

Bit operators

&
Bit-wise AND: The bits of the value on the left and the value on the right are ANDed.
|
Bit-wise OR: The bits of the value on the left and the value on the right are ORed.
^
Bit-wise exclusive OR: The bits of the value on the left and the value on the right are exclusively ORed.

Relational operators

==
Equal: If the value on the left is equal to the value on the right, then the result is 1; otherwise the result is 0.
!=
Not equal: If the value on the left isn't equal to the value on the right, then the result is 1; otherwise the result is 0.
<
Less than: If the value on the left is less than the value on the right, then the result is 1; otherwise the result is 0.
<=
Less than or equal: If the value on the left is less than or equal to the value on the right, then the result is 1; otherwise the result is 0.
>
Greater than: If the value on the left is greater than the value on the right, then the result is 1; otherwise the result is 0.
>=
Greater than or equal: If the value on the left is greater than or equal to the value on the right, then the result is 1; otherwise the result is 0.

Arithmetic/logical shift operators

<<
Left shift: The bits of the value on the left are shifted to the left by the amount described by the value on the right.
>>
Right shift: The bits of the value on the left are shifted to the right by the amount described by the value on the right. If the object on the left is described as unsigned, the vacated high-order bits are zeroed. If the object on the left is described as signed, the sign bit is propagated through the vacated high-order bits. The debugger treats registers as unsigned items.

Binary arithmetic operators

+
Addition: The value on the right is added to the value on the left.
-
Subtraction: The value on the right is subtracted from the value on the left.
*
Multiplication: The value on the left is multiplied by the value on the right.
/
Division: The value on the left is divided by the value on the right.
%
Modulus: The modulus of the value on the left with respect to the value on the right is produced. The result is the remainder when the value on the left is divided by the value on the right.

Unary arithmetic operators

+
Plus: The result is the value on the right.
-
Minus: The result is the negation of the value on the right.
~
Bit-wise complement: The result is the bit-wise complement of the value on the right.
!
Logical complement: If the value on the right is equal to 0, then the result is 1; otherwise it is 0.
++
Increment: Both prefix and postfix operators are supported. If the object is on the right, it is pre-incremented by 1 (for example, ++x). If the object is on the left, it is post-incremented by 1 (for example, x++).
--
Decrement: Both prefix and postfix operators are supported. If the object is on the right, it is pre-decremented by 1 (for example, --x). If the object is on the left, it is post-decremented by 1 (for example, x--).
&
Address of: The result is the address (segment:offset) of the object on the right (for example, &main).
*
Points: The result is the value stored at the location addressed by the value on the right (for example, *(ds:100), *string.loc). In the absence of typing information, a near pointer is produced. If the operand doesn't have a segment specified, the default data segment (DGROUP) is assumed:

          (SS:00FE) = FFFF
    var:  (SS:0100) = 0152
          (SS:0102) = 1240
          (SS:0104) = EEEE
        
%
Value at address: The result is the value stored at the location addressed by the value on the right (for example, %(ds:100), %string.loc). In the absence of typing information, a far pointer is produced. If the operand doesn't have a segment specified, the default data segment (DGROUP) is assumed:

          (SS:00FE) = FFFF
    var:  (SS:0100) = 0152
          (SS:0102) = 1240
          (SS:0104) = EEEE
        

Note: This operator isn't found in the C or C++ programming languages.

Special unary operators

sizeof unary_expression
For example:

    sizeof tyme
    sizeof (*tyme)
        
sizeof(type_name)
For example:

    sizeof( struct tm )
        
(type_name) unary_expression
The type conversion operator (type_name) is used to convert an item from one type to another. The following describes the syntax of type_name:

    type_name ::= type_spec { [ "near" | "far" | 
                                "huge" ] "*" }
    type_spec ::= typedef_name
            |   "struct" structure_tag
            |   "union"  union_tag
            |   "enum"   enum_tag
            |   scalar_type { scalar_type }
    scalar_type ::= "char" | "int" | "float" 
            | "double" |   "short" | "long" 
            | "signed" | "unsigned"
        

For example:

    (float) 4
    (int) 3.1415926
        
[type_name] unary_expression
You can force the debugger to treat a memory reference as a particular type of value by using a type coercion operator. A type specification is placed inside brackets as shown above. The basic types are:
char
character, 8 bits
short
short integer, 16 bits
long
long integer, 32 bits
float
single-precision floating-point, 32 bits
double
double-precision floating-point, 64 bits

Unless qualified by the short or long keyword, the int type is 16 bits in 16-bit applications and 32 bits in 32-bit applications (386, 486, and Pentium systems). The character, short integer, and long integer types may be treated as signed or unsigned items. The default for the character type is unsigned. The default for the integer types is signed. For example:

    [char]             (default is unsigned)
    [signed char]
    [unsigned char]
    [int]              (default is signed)
    [short]            (default is signed)
    [short int]        (default is signed)
    [signed short int]
    [long]             (default is signed)
    [long int]         (default is signed)
    [signed long]
    [unsigned long int]
    [float]
    [double]
        

Note that it's unnecessary to specify the int keyword when short or long is specified.

?
Existence test: The ? unary operator may be used to test for the existence of a symbol. For example:

    ?id
        

The result of this expression is 1 if id is a symbol known to the debugger, and 0 otherwise. If the symbol doesn't exist in the current scope, then it must be qualified with its module name. Automatic symbols exist only in the current function.

Binary address operator

:
Memory locations can be referenced by using the binary : (colon) operator and a combination of constants, register names, and symbol names. In the Intel 80x86 architecture, a memory reference requires a segment and offset specification. A memory reference using the : operator takes the following form:

segment:offset

The elements segment and offset can be expressions. For example:

    (ES):(DI+100)
    (SS):(SP-20)
        

Primary expression operators

[ ]
Elements of an array can be identified using subscript expressions. Consider the following 3-dimensional array defined in the C language:

    char *ProcessorType[2][4][2] =
        { { { "Intel 8086",   "Intel 8088"  },
            { "Intel 80186",  "Intel 80188" },
            { "Intel 80286",  "unknown"     },
            { "Intel 80386",  "unknown"     } },

          { { "NEC V30",      "NEC V20"     },
            { "unknown",      "unknown"     },
            { "unknown",      "unknown"     },
            { "unknown",      "unknown"     } } };
        

This array can be viewed as two layers of rectangular matrices of 4 rows by 2 columns. The array elements are all pointers to string values.

By using a subscript expression, specific slices of an array can be displayed. To see only the values of the first layer, the following expression can be issued:

    processortype[0]
        

To see only the first row of the first layer, the following expression can be issued:

    processortype[0][0]
        

To see the second row of the first layer, the following command can be issued:

    processortype[0][1]
        

To see the value of a specific entry in a matrix, all the indexes can be specified:

    processortype[0][0][0]
    processortype[0][0][1]
    processortype[0][1][0]
        
()
The function call operators appear to the right of a symbol name and identify a function call in an expression. The parentheses can contain arguments. For example:

    ClearScreen()
    PosCursor( 10, 20 )
    Line( 15, 1, 30, '-', '+', '-' )
        
.
The . (dot) operator indicates field selection in a structure. In the following example, tyme2 is a structure, and tm_year is a field in the structure:

    tyme2.tm_year
        
->
The -> operator indicates field selection when using a pointer to a structure. In the following example, tyme is the pointer, and tm_year is a field in the structure to which it points:

    tyme->tm_year
        

Operators for the C++ grammar

Debugger support for the C++ grammar includes all of the C operators described in the previous section ``Operators for the C Grammar.'' In addition to this, the debugger supports a variety of C++ operators in the C++ Programming Language manual.

Perhaps the best way to illustrate the additional capabilities of the debugger's support for the C++ grammar is by way of an example. The following C++ program encompasses the features of C++ that we use in our debugging example:

// DBG_EXAM.C: C++ debugging example program

struct BASE {
    int a;
    BASE() : a(0) {}
    ~BASE(){}
    BASE & operator =( BASE const &s )
    {
        a = s.a;
        return *this;
    }
    virtual void foo()
    {
        a = 1;
    }
};

struct DERIVED : BASE {
    int b;
    DERIVED() : b(0) {}
    ~DERIVED() {}
    DERIVED & operator =( DERIVED const &s )
    {
        a = s.a;
        b = s.b;
        return *this;
    }
    virtual void foo()
    {
        a = 2;
        b = 3;
    }
    virtual void foo( int )
    {
    }
};

void use( BASE *p )
{
    p->foo();
}

void main()
{
    DERIVED x;
    DERIVED y;

    use( &x );
    y = x;
}

Compile and link this program so that the most comprehensive debugging information is included in the executable file.

Ambiguity resolution

Continuing with the example of the previous section, we can step into the call to use() and up to the p->foo() function call. Try to set a breakpoint at foo.

You'll be presented with a window containing a list of foo functions to choose from since the reference to foo at this point is ambiguous. Select the one that interests you.

You may also have observed that, in this instance, p is really a pointer to the variable x, which is a DERIVED type. To display all the fields of x, you can typecast it as follows.

    *(DERIVED *)p

The this operator

Continuing with the example of the previous sections, we can step into the call to f->foo()() and up to the b=3 statement. You can use the this operator as illustrated in the following example.

    this->a
    *this

Operator functions

Continuing with the example of the previous sections, we can set breakpoints at C++ operators using expressions similar to the following:

    operator =

For example:

    DERIVED & operator =( DERIVED const &s )
    {
        a = s.a;
        b = s.b;
        return *this;
    }

Scope operator ::

We can use the scope operator :: to identify what it is that we wish to examine. Continuing with the example of the previous sections, we can enter an address like:

    base::foo

In some cases, this also helps resolve any ambiguity. The example above permits us to set a breakpoint at the source code for the function foo() in the class BASE.

    virtual void foo()
    {
        a = 1;
    }

Here are some more interesting examples:

    derived::foo
    derived::operator =

The first of these two examples contains an ambiguous reference, so a prompt window is displayed to resolve the ambiguity.

Constructor and destructor functions

We can also examine the constructor and destructor functions of an object or class. Continuing with the example of the previous sections, we can enter expressions like:

    base::base
    base::~base

The examples above permit us to refer to the source code for the constructor and destructor functions in the class BASE.

Operators for the FORTRAN grammar

The debugger supports most FORTRAN 77 operators and includes an additional set of operators for convenience. The additional operators are patterned after those available in the C programming language.


Note: QNX doesn't support FORTRAN 77. The operators described in this section are useful only if you're remotely debugging an application on a platform that does support it.

The grammar that the debugger supports is close to that of the FORTRAN 77 language, but there are a few instances where space characters must be used to clear up any ambiguities. For example, the expression:

    1.eq.x

results in an error, since the debugger forms a floating-point constant from the 1., leaving the string eq.x. If you introduce a space after the 1, then you clear up the ambiguity:

    1 .eq.x

Unlike FORTRAN, the parser in the debugger treats spaces as significant characters. Thus spaces must not be introduced in the middle of symbol names, constants, multi-character operators like .EQ. or //, and so on.

Operators are presented in order of precedence, from lowest to highest. Operators on the same line have the same priority.

Assignment Operators (lowest priority):

    =  +=  -=  *=  /=  %=  &=  |=  ^=  <<=  >>=

Logical Operators:

    .EQV.  .NEQV.
    .OR.
    .AND.
    .NOT.

Bit Operators:

    |
    ^
    &

Relational Operators:

    .EQ.  .NE.	.LT.  .LE.  .GT.  .GE.

Shift and Concatenation Operators:

    <<	>>  //

Arithmetic Operators:

    +  -
    *  /  %
    ** (unsupported)

Unary Operators:

    +  -
    ~  ++  --  &  *  %
    [type_name] unary_expr
    ?

Binary Address Operator (highest priority):

    :

Parentheses can be used to order the evaluation of an expression.

In addition to the operators listed above, a number of primary expression operators are supported. These operators are used in identifying the object to be operated upon:

()
subscripting, substringing, or function call
.
field selection
->
field selection using a pointer

The following built-in functions may be used to convert the specified argument to a particular type:

INT()
conversion to integer
REAL()
conversion to real
DBLE()
conversion to double-precision
CMPLX()
conversion to complex
DCMPLX()
conversion to double-precision complex

The sections that follow describe the operators presented above.

Assignment operators

=
Assignment: The value on the right is assigned to the object on the left.
+=
Additive assignment: The object on the left is augmented by the value on the right.
-=
Subtractive assignment: The object on the left is reduced by the value on the right.
*=
Multiplicative assignment: The object on the left is multiplied by the value on the right.
/=
Division assignment: The object on the left is divided by the value on the right.
%=
Modulus assignment: The object on the left is updated with MOD(left, right). The result is the remainder when the value of the object on the left is divided by the value on the right.
&=
Bit-wise AND: The bits in the object on the left are ANDed with the bits of the value on the right.
|=
Bit-wise inclusive OR: The bits in the object on the left are ORed with the bits of the value on the right.
^=
Bit-wise exclusive OR: The bits in the object on the left are exclusively ORed with the bits of the value on the right.
<<=
Left shift: The bits in the object on the left are shifted to the left by the amount of the value on the right.
>>=
Right shift: The bits in the object on the left are shifted to the right by the amount of the value on the right. If the object on the left is described as unsigned, the vacated high-order bits are zeroed. If the object on the left is described as signed, the sign bit is propagated through the vacated high-order bits.
Note: The debugger treats registers as unsigned items.

Logical operators

.EQV.
Logical equivalence: The logical equivalence of the value on the left and the value on the right is produced.
.NEQV.
Logical non-equivalence: The logical non-equivalence of the value on the left and the value on the right is produced.
.OR.
Logical inclusive disjunction: The logical OR of the value on the left and the value on the right is produced.
.AND.
Logical conjunction: The logical AND of the value on the left and the value on the right is produced.
.NOT.
Logical negation: The logical complement of the value on the right is produced.

Bit operators

|
Bit-wise OR: The bits of the value on the left and the value on the right are ORed.
^
Bit-wise exclusive OR: The bits of the value on the left and the value on the right are exclusively ORed.
&
Bit-wise AND: The bits of the value on the left and the value on the right are ANDed.

Relational operators

.EQ.
Equal: If the value on the left is equal to the value on the right, then the result is 1; otherwise the result is 0.
.NE.
Not equal: If the value on the left isn't equal to the value on the right, then the result is 1; otherwise the result is 0.
.LT.
Less than: If the value on the left is less than the value on the right, then the result is 1; otherwise the result is 0.
.LE.
Less than or equal: If the value on the left is less than or equal to the value on the right, then the result is 1; otherwise the result is 0.
.GT.
Greater than: If the value on the left is greater than the value on the right, then the result is 1; otherwise the result is 0.
.GE.
Greater than or equal: If the value on the left is greater than or equal to the value on the right, then the result is 1; otherwise the result is 0.

Arithmetic/logical shift operators

<<
Left shift: The bits of the value on the left are shifted to the left by the amount described by the value on the right.
>>
Right shift: The bits of the value on the left are shifted to the right by the amount described by the value on the right. If the object on the left is described as unsigned, the vacated high-order bits are zeroed. If the object on the left is described as signed, the sign bit is propagated through the vacated high-order bits.
Note: The debugger treats registers as unsigned items.

Concatenation operator

//
String concatenation: The concatenation of the character string value on the left and right is formed.

Binary arithmetic operators

+
Addition: The value on the right is added to the value on the left.
-
Subtraction: The value on the right is subtracted from the value on the left.
*
Multiplication: The value on the left is multiplied by the value on the right.
/
Division: The value on the left is divided by the value on the right.
%
Modulus: The modulus of the value on the left with respect to the value on the right is produced. The result is the remainder when the value on the left is divided by the value on the right.
**
Exponentiation
Note: Exponentiation isn't supported by the debugger.

Unary arithmetic operators

+
Plus: The result is the value on the right.
-
Minus: The result is the negation of the value on the right.
~
Bit-wise complement: The result is the bit-wise complement of the value on the right.
++
Increment: Both prefix and postfix operators are supported. If the object is on the right, it's pre-incremented by 1 (for example, ++x). If the object is on the left, it's post-incremented by 1 (for example, x++).
--
Decrement: Both prefix and postfix operators are supported. If the object is on the right, it's pre-decremented by 1 (for example, --x). If the object is on the left, it's post-decremented by 1 (for example, x--).
&
Address of: The result is the address (segment:offset) of the object on the right (for example, main).
*
Points: The result is the value stored at the location addressed by the value on the right (for example, *(ds:100), *string.loc). In the absence of typing information, the value on the right is treated as a pointer into the default data segment (DGROUP) and a near pointer is produced:
          (SS:00FE) = FFFF
    var:  (SS:0100) = 0152
          (SS:0102) = 1240
          (SS:0104) = EEEE
        
%
Value at address: The result is the value stored at the location addressed by the value on the right (for example, %(ds:100), %string.loc). In the absence of typing information, the value on the right is treated as a pointer into the default data segment (DGROUP) and a far pointer is produced:
          (SS:00FE) = FFFF
    var:  (SS:0100) = 0152
          (SS:0102) = 1240
          (SS:0104) = EEEE
        

Note: The % operator isn't found in the FORTRAN 77 programming language.

Special unary operators

?
Existence test: The ? unary operator may be used to test for the existence of a symbol. For example:

    ?id
        

The result of this expression is 1 if id is a symbol known to the debugger; 0 if not. If the symbol doesn't exist in the current scope, then it must be qualified with its module name. Automatic symbols exist only in the current subprogram.

Binary address operator

:
Memory locations can be referenced by using the binary : operator and a combination of constants, register names, and symbol names. In the Intel 80x86 architecture, a memory reference requires a segment and offset specification. A memory reference using the : operator takes the following form:

    segment:offset
        

The elements segment and offset can be expressions. For example:

    (ES):(DI+100)
    (SS):(SP-20)
        

Primary expression operators

()
The () operator is used for:
  • subscripts for an array
  • substrings
  • function calls
.
The . operator indicates field selection in a structure. This operator is useful in mixed-language applications where part of the application is written in the C or C++ programming language. In the following example, tyme2 is a structure, and tm_year is a field in the structure.

    tyme2.tm_year
        
->
The -> operator indicates field selection when using a pointer to a structure. This operator is useful in mixed-language applications where part of the application is written in the C or C++ programming language. In the following example, tyme is the pointer, and tm_year is a field in the structure it points to.

    tyme->tm_year