CLOSE

In C programming, especially in systems programming and embedded systems, precise control over data types is essential. This is where the <stdint.int> library comes into play. This header file, part of the C standard library, provides a set of typedefs that define integer types with specific widths, ensuring portability and consistency across different platforms.

The Importance of Fixed-Width Integer Types

When developing software, particularly low-level systems software like operating systems, the exact size of integer types can be critical. For example, you might need an integer that is exactly 8 bits wide or a 32-bit integer for interfacing with hardware registers. Without <stdint.h>, you would have to rely on the implementation-defined sizes of types like int and long, which can vary between platforms.

Content of Minimal stdint.h Header File

#ifndef __STDINT_H__
#define __STDINT_H__


// Fixed-width integer types
// 8 bits = 1 byte
typedef signed char int8_t;
typedef unsigned char uint8_t;


// 16 bits = 2 bytes
typedef short int16_t;
typedef unsigned short uint16_t;


// 32 bits = 4 bytes
typedef int int32_t;
typedef unsigned int uint32_t;

// 64 bits = 8 bytes
typedef long long int int64_t;
typedef unsigned long long uint64_t;



#endif /* __STDINT_H__ */

uintptr_t:

It is an unsigned integer type in C/C++ designed to be able to store a pointer. It is an unsigned integer type guaranteed to be large enough to hold any pointer to data.

  • It is defined in <stdint.h> header file.
  • Its definition depends on the:
    • uint64_t
    • uint32_t

Characteristics of uintptr_t:

  • Size Compatibility: The size of uintptr_t is the same as that of a pointer on the target platform. For instance, on a 32-bit system, uintptr_t is typically a 32-bit unsigned integer, while on a 64-bit system, it is a 64-bit unsigned integer.
  • Type Safety: While uintptr_t is an integer type, it ensures that pointers can be safely cast to and from this type, preserving the address value without modification.
  • Unsigned Nature: Being an unsigned type, uintptr_t can represent the full range of pointer values without negative values, which is often useful in low-level memory manipulation.

Implementation:

// uintptr_t declaration
/*
* uintptr_t is used for casting pointer to integer types
* and is defined to be large enough to hold a pointer on
* the target platform.
* On 32-bit systems: It is typically a 32-bit unsigned int.
* On 64-bit systems: It is typically a 64-bit unsigned int.
*/
// __SIZEOF_POINTER__ is used by GCC and clang (but not by MSVC - Microsoft Visual C++) to indicate the size of pointer in bytes.
#ifdef __SIZEOF_POINTER__
	#if __SIZEOF_POINTER__ == 8
		typedef uint64_t uintptr_t;	// 64-bit pointer size
	#elif __SIZEOF_POINTER__ == 4
		typedef uint32_t uintptr_t;	// 32-bit pointer size
	#else
		#error "Unsupported pointer size"
	#endif
#else
	// GCC/Clang: _x86_64 is predefined macro to determine the target is 32-bit or 64-bit
	// MSVC: _WIN64 for 64-bit targets and absence of _WIN64 for 32-bit targets.
	#if defined(__x86_64__) || defined(_WIN64)
		typedef uint64_t uintptr_t; // 64-bit
	#else
		typedef uint32_t uintptr_t; // 32-bit
	#endif
#endif

Explanation:

This code dynamically defines uintptr_t based on the target platform's pointer size. It first tries to use __SIZEOF_POINTER__ to determine the pointer size, and if that macro is not available, it falls back to checking common predefined macros for 32-bit and 64-bit targets. This approach ensures uintptr_t is correctly defined whether the code is compiled with GCC, Clang, or MSVC, making it portable across different compilers and platforms.

  1. Check for __SIZEOF_POINTER__:
    1. __SIZEOF_POINTER__ is a predefined macro in GCC and Clang that indicates the size of a pointer in bytes.
    2. The #ifdef __SIZEOF_POINTER__ directive checks if this macro is defined.
      1. If __SIZEOF_POINTER__ is defined:
        1. #if __SIZEOF_POINTER__ == 8: If the size of a pointer is 8 bytes (64 bits), uintptr_t is defined as uint64_t (which is unsigned long long).
        2. #elif __SIZEOF_POINTER__ == 4: If the size of a pointer is 4 bytes (32 bits), uintptr_t is defined as uint32_t (which is unsigned int).
        3. #else: If the pointer size is not 4 or 8 bytes, a compilation error is generated, indicating an unsupported pointer size.
  2. Fallback Check for Target Architecture:
    1. If __SIZEOF_POINTER__ is not defined (for compilers like MSVC which may not define it), the code checks predefined macros for the target architecture:
      1. #if defined(__x86_64__) || defined(_WIN64): Checks if the target is 64-bit:
        1. __x86_64__ is predefined by GCC/Clang for 64-bit systems.
        2. _WIN64 is predefined by MSVC for 64-bit systems.
        3. If either macro is defined, uintptr_t is defined as uint64_t (which is unsigned long long).
        4. #else: If neither 64-bit macro is defined, it assumes a 32-bit target and defines uintptr_t as uint32_t (which is unsigned int).
Start
  ├── Is `__SIZEOF_POINTER__` defined?
  │        ├── Yes
  │        │        ├── Is `__SIZEOF_POINTER__` == 8?
  │        │        │        ├── Yes
  │        │        │        │        └── typedef uint64_t uintptr_t; // 64-bit pointer size
  │        │        │        └── No
  │        │        │                ├── Is `__SIZEOF_POINTER__` == 4?
  │        │        │                │        ├── Yes
  │        │        │                │        │        └── typedef uint32_t uintptr_t; // 32-bit pointer size
  │        │        │                └── No
  │        │        │                        └── #error "Unsupported pointer size"
  │        └── No
  │                ├── Is the target architecture 64-bit?
  │                │        ├── defined(__x86_64__) or defined(_WIN64)?
  │                │        │        ├── Yes
  │                │        │        │        └── typedef uint64_t uintptr_t; // 64-bit pointer size
  │                │        │        └── No
  │                │        │                └── typedef uint32_t uintptr_t; // 32-bit pointer size