Originally posted by: steven.zhang373
z/OS XL C/C++ compiler supports the "__ptr32" type qualifier to facilitate porting structures with pointer members from 32-bit mode to 64-bit mode.
However, if you fail to use it in the right way, it cause unexpected results at run time that are hard to debug.
Let's take the following code as an example, and think about why we can't get the expected result.
> cat test.C
#define PTR_TYPE char * __ptr32
PTR_TYPE p = (PTR_TYPE)(~0);
PTR_TYPE * __ptr32 addr = &p;
if((((unsigned int)(*addr)) & 0x80000000) != 0x80000000)
> xlC test.C -q64
> echo $?
0 <------ OOPS: I expect it should be 1 !
"addr" is a 32-bit pointer, which points to an object of type "PTR_TYPE". This object is also a 32-bit pointer. This programmer is deferencing "addr" and testing its high order bit to see if it is ON. Unfortunately, the test never succeeds even though its value is set to 0xFFFFFFFF. Isn't it weird? The test case is so straightforward. Is the compiler doing something wrong?
In fact, the type of "*addr" is PTR_TYPE, which is a pointer qualified with the "__ptr32" qualifier, and it is converted to "unsigned int". The example code actually assumes that it is safe to convert an “char __ptr32*” object to “unsigned int” without losing any precision. Is it true ?
To answer this question, we need to understand what is "__ptr32".
From IBM Knowledge Center (http://www-01.ibm.com/support/knowledgecenter/SSLTBW_1.12.0/com.ibm.zos.r12.cbclx01/zq_pt32.htm), we know that, the keyword __ptr32 is a qualifier that can be applied to a pointer type to constrain its size to 32 bits. So, we use it to constrain the pointer to be 32 bits in size and then convert it to integer, which is also 32 bits in size. So far so good. However, there are some extra notes in the documentation about what happens in 64-bit mode. “In 64-bit mode, the size of a pointer is eight bytes, and all 64 bits participate in addressing. However, when a __ptr32 pointer is dereferenced, a 64-bit address is formed by filling the 33 high-order bits with zeros.” Only the 31 bits of the address are used when it is qualified with __ptr32 in 64-bit mode! To understand why we only use the 31-bit addressing rather than the 32-bit addressing mode, we need to understand another addressing mode.
In 1964, IBM announced a popular computer system with 24-bit addressing and 32-bit general registers and arithmetic, named System/360. As time went on, IBM tried to upgrade the 24-bit addressing to 32-bit addressing. However, assembly language programmers, including IBM’s own operating system architects and developers, had been using the spare byte at the top of the address (the top 8 bit) for flags for almost twenty years. In order to maintain compatibility with 24-bit addressing, IBM chose to provide two forms of addressing to minimize the pain: if the most significant bit (bit 0) of a 32-bit address was on, the next 31 bits were interpreted as the virtual address; if the most significant bit was off, then only the lower 24 bits were treated as the virtual address (just as with pre-XA systems). Thus programmers could continue using the seven low-order bits of the top byte for other purposes as long as they left the top bit off. The only programs requiring modification were those that set the top (leftmost) bit of a word containing an address. Therefore, in 1983 IBM introduced 31-bit addressing in the System/370-XA mainframe architecture. The 31-bit addressing was invented to support compatibility with old 24-bit addressing.
So, for the example case, though the size of the “__ptr32” qualified pointer is four bytes, the high-order bit is reserved for system use, and is not used to form the address. In other words, it couldn’t be used by the programmer. And it is always cleared to zero by the compiler.
The language standard doesn't specify the size of each predefined data type, therefore it is not recommended to cast a pointer address to unsigned int. And such practice is prone to making your code difficult to port to other platforms. No one said that the size of integer must be 4 bytes. Thus, why do you use mask “0x80000000” to test it ?
To be a powerful programmer, we need to understand the hardware, language standard, even and language extensions.
English Editor: Shuai Cao (Erik). Many thanks to Erik!