Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Sign Extension of C++ Pointer: The Cause of NIC’s Failure to Send Packets

4.89/5 (4 votes)
20 Feb 2014CPOL2 min read 11.2K  
Sign Extension of C++ Pointer: The cause of NIC’s failure to send packets

I’ve been working on a NIC driver project on Pharlap. Due to some reason, it can’t send out any packets. I took some time troubleshooting the problem and finally it turned out to be the incorrect setting of the following hardware register.

C++
// Memory-mapped buffer
 struct Registers
 {
    // High 32 bits of transmit descriptor base address
    unsigned long TxDescBaseHi;
    // Low 32 bits of transmit descriptor base address
    unsigned long TxDescBaseLo;
    ..
 };

The register consists of two 32-bit registers which are written by the software driver during initialization to tell the hardware about the starting address of the transmit descriptors. Transmit descriptor is a structure storing the address of the packet to send, along with some control information. The two registers are there to support 64-bit addressing system.

The initialization code is like:

C++
TxDescriptor *m_txRing = m_DMABuffer.Get(SIZE, ALIGNMENT)
..
u64 baseAddr = reinterpret_cast<u64>(m_txRing);
m_pReg->TxDescBaseHi = baseAddr >> 32;
m_pReg->TxDescBaseLo = static_cast<u32>(baseAddr);

The code is simple to be self-describing. However, it’s where the problem is. Since the Pharlap I’m working on is 32-bit, the pointer m_txRing is 4-byte long. Here are the printed values of these variables.

C++
m_txRing: 0x80236400
m_pReg->TxDescBaseHi:0xFFFFFFFF
m_pReg->TxDescBaseLo:0x80236400

As you can see, the value in high register is not what we expect. 32-bit pointer m_txRing is sign extended. In C++/C, converting a pointer to an integer is implementation-defined.

"A pointer can be explicitly converted to any integral type large 
enough to hold it. The mapping function is implementation-defined."
                                                          - C++03

In MSVC/GCC, if you cast a pointer to an integer of a bigger size, the pointer is first sign extended, and the resulting value is then converted to the integer. In this case, 0×80236400 is sign extended to 0xFFFFFFFF80236400, and since it’s in the range of u64, it’s the final value.

The solution is to use uintptr_t. uintptr_t is an alias(typedef) of an unsigned integer which has the same size of a pointer(Note that intptr_t/uintptr_t is optional in C++11). First, convert the pointer to uintptr_t. There is no extension here. Second, convert the result to target integer.

A few takeaways:

  1. Don’t convert between pointer and integer unless it’s essential. When the time comes, use uintptr_t. Convert the pointer to uintptr_t first, to avoid possible sign extension.
  2. reinterpret_cast is not guaranteed by the standard to have the same bit pattern as the original although it’s a common practice. Sign extension in this case, is one place where it doesn’t hold, kind of.
"The mapping performed by reinterpret_cast might, or might not, 
produce a representation different from the original value. "
                                                      - C++03

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)