Keywords: C programming | printf formatting | unsigned long long int | format specifiers | embedded systems
Abstract: This technical paper provides an in-depth analysis of printf formatting for unsigned long long int in C programming. Through detailed examination of common formatting errors and their solutions, the paper explains the correct usage of %llu format specifier and compares format specifiers for different integer types. The discussion extends to embedded systems development, examining support differences in various C standard library implementations like Newlib and NewlibNano for 64-bit integer and floating-point formatting, with complete code examples and practical solutions.
Problem Background and Common Errors
In C programming, formatted output is one of the fundamental input/output operations. However, when dealing with integer types of different sizes, particularly the 64-bit unsigned integer type unsigned long long int, developers often encounter formatting issues.
Consider the following typical error example:
#include <stdio.h>
int main() {
unsigned long long int num = 285212672;
int normalInt = 5;
printf("My number is %d bytes wide and its value is %ul. A normal number is %d.\n",
sizeof(num), num, normalInt);
return 0;
}
The output of this code is:
My number is 8 bytes wide and its value is 285212672l. A normal number is 0.
Two obvious problems can be observed: the value of the unsigned long long int variable is displayed incorrectly, and the subsequent normalInt variable shows 0 instead of the expected 5. This error stems from using the incorrect format specifier %ul.
Correct Formatting Approach
For the unsigned long long int type, the correct format specifier is %llu. Where:
llis the long long modifier, indicatinglong longtypeuis the unsigned integer conversion specifier
The corrected code example:
#include <stdio.h>
int main() {
unsigned long long int num = 285212672;
int normalInt = 5;
printf("My number is %d bytes wide and its value is %llu. A normal number is %d.\n",
sizeof(num), num, normalInt);
return 0;
}
Now the output will correctly display:
My number is 8 bytes wide and its value is 285212672. A normal number is 5.
Complete Reference for C Integer Type Format Specifiers
To assist developers in correctly using format specifiers for various integer types, here is a complete reference list:
%d --> for int type
%u --> for unsigned int type
%ld --> for long int or long type
%lu --> for unsigned long int, long unsigned int, or unsigned long type
%lld --> for long long int or long long type
%llu --> for unsigned long long int or unsigned long long type
Special Considerations in Embedded Systems
In embedded systems development, particularly when using different C standard library implementations, support for formatted output may vary. Reference article 3 provides a case study using different libraries in the MCUXpresso environment.
When using the NewlibNano library, you might encounter lack of support for 64-bit integer formatting:
#include <stdio.h>
#include <stdint.h>
int main() {
uint64_t i = 0xfedcba9876543210;
printf("i = %llu (0x%llx)\n", (unsigned long long int)i, (unsigned long long int)i);
return 0;
}
Output with NewlibNano might be:
i = lu (0xlx)
While with the full Newlib library, the output correctly displays:
i = 18364758544493064720 (0xfedcba9876543210)
Type Sizes and Platform Dependencies
It's important to note that integer type sizes may vary across different platforms. In 32-bit systems, long int is typically 32-bit, while in 64-bit systems it might be 64-bit. However, long long int is always at least 64-bit on all platforms.
You can check type sizes using the sizeof operator:
#include <stdio.h>
#include <stdint.h>
int main() {
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of long: %zu bytes\n", sizeof(long));
printf("Size of long long: %zu bytes\n", sizeof(long long));
printf("Size of uint64_t: %zu bytes\n", sizeof(uint64_t));
return 0;
}
Best Practices and Recommendations
1. Always Use Correct Format Specifiers: Ensure format specifiers exactly match parameter types to avoid undefined behavior.
2. Use Standard Type Definitions: When specific integer sizes are required, prefer types defined in <stdint.h> such as uint64_t, int32_t, etc.
3. Consider Library Compatibility: In embedded development, understand the support for specific features in your chosen C standard library.
4. Enable Compiler Warnings: Use compiler options like -Wformat to detect format specifier mismatches.
5. Cross-Platform Considerations: Pay special attention to integer type sizes and format specifier differences when writing cross-platform code.
By following these best practices, developers can avoid common formatting errors and write more robust and portable C code.