5
\$\begingroup\$

I intend to demonstrate the proper way to use format specifiers for different data types and also display the respective limits. This is meant to be an example.

limitdemo.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>
#include <string.h>

#define LEN "20"

const char line_char = '=';

void print_line(const size_t len)
{
    for (size_t index = 0; index < len; ++index) {
        putchar(line_char);
    }
    putchar('\n');
}

int main()
{
    // For Unix-based systems, replace %[...]Iu with %[...]zu
    // The standard types - signed
    printf("\n\n%-9s | Bytes | %"LEN"s | %"LEN"s\n", "Type", "Minimum", "Maximum");
    print_line(63);
    printf("char      | %5Iu | %"LEN"hhd | %"LEN"hhd\n", sizeof(char), CHAR_MIN, CHAR_MAX);
    printf("short     | %5Iu | %"LEN"hd | %"LEN"hd\n", sizeof(char), SHRT_MIN, SHRT_MAX);
    printf("int       | %5Iu | %"LEN"d | %"LEN"d\n", sizeof(int), INT_MIN, INT_MAX);
    printf("long      | %5Iu | %"LEN"ld | %"LEN"ld\n", sizeof(long), LONG_MIN, LONG_MAX);
    printf("long long | %5Iu | %"LEN"lld | %"LEN"lld\n", sizeof(long long), LLONG_MIN, LLONG_MAX);
    
    // The standard types - unsigned
    printf("\n\n%-18s | Bytes | %"LEN"s\n", "Type", "Maximum");
    print_line(49);
    printf("unsigned char      | %5Iu | %"LEN"hhu\n", sizeof(unsigned char), UCHAR_MAX);
    printf("unsigned short     | %5Iu | %"LEN"hu\n", sizeof(unsigned char), USHRT_MAX);
    printf("unsigned int       | %5Iu | %"LEN"u\n", sizeof(unsigned int), UINT_MAX);
    printf("unsigned long      | %5Iu | %"LEN"lu\n", sizeof(unsigned long), ULONG_MAX);
    printf("unsigned long long | %5Iu | %"LEN"llu\n", sizeof(unsigned long long), ULLONG_MAX);

    // The fixed-width types signed and unsigned
    printf("\n\nWidth  | %"LEN"s | %"LEN"s\n", "Signed Max", "Unsigned Max");
    print_line(52);
    printf(" 8 bit | %"LEN"hhd | %"LEN"hhu\n", INT8_MAX, UINT8_MAX);
    printf("16 bit | %"LEN"hd | %"LEN"hu\n", INT16_MAX, UINT16_MAX);
    printf("32 bit | %"LEN"d | %"LEN"u\n", INT32_MAX, UINT32_MAX);
    printf("64 bit | %"LEN"lld | %"LEN"llu\n", INT64_MAX, UINT64_MAX);
    return EXIT_SUCCESS;
}

Note: For Unix-based (non-Windows) systems, use zu instead of Iu.

Output (may vary on your system)

Type      | Bytes |              Minimum |              Maximum
===============================================================
char      |     1 |                 -128 |                  127
short     |     1 |               -32768 |                32767
int       |     4 |          -2147483648 |           2147483647
long      |     4 |          -2147483648 |           2147483647
long long |     8 | -9223372036854775808 |  9223372036854775807


Type               | Bytes |              Maximum
=================================================
unsigned char      |     1 |                  255
unsigned short     |     1 |                65535
unsigned int       |     4 |           4294967295
unsigned long      |     4 |           4294967295
unsigned long long |     8 | 18446744073709551615


Width  |           Signed Max |         Unsigned Max
====================================================
 8 bit |                  127 |                  255
16 bit |                32767 |                65535
32 bit |           2147483647 |           4294967295
64 bit |  9223372036854775807 | 18446744073709551615

My question

How do I make the code less repetitive and better organized? I would also be grateful for advice on any aspect of the code, suggestions for alternate approach or better ways of doing it. That includes a better way to render the tables.

\$\endgroup\$
  • 2
    \$\begingroup\$ Your program invokes undefined behavior for the fixed-width types. See stackoverflow.com/a/9225648. \$\endgroup\$ – Roland Illig Dec 31 '17 at 11:09
  • \$\begingroup\$ @Roland You're right about using the PRI Macros. I forgot to use them. \$\endgroup\$ – Hungry Blue Dev Dec 31 '17 at 11:12
  • 1
    \$\begingroup\$ You have a copy paste bug: notice your program output shows Bytes for short as 1 instead of 2 (same for unsigned short). \$\endgroup\$ – JS1 Jan 1 '18 at 21:42
4
\$\begingroup\$

How do I make the code less repetitive and better organized?

  1. Rather then bury magic numbers 9, 18, 20 in format specifiers, consider "*" for the width. Then compiled code could pass in the computed width.

  2. Similar thought for print_line(63);

    printf("\n\n");
    int nn = printf("%-9s | Bytes | %"LEN"s | %"LEN"s\n", "Type", inimum", "Maximum");
    print_line(nn);
    
  3. Not too easy to re-organize too much without violating coding goal of "demonstrate the proper way to use format specifiers for different data types". Perhaps a helper function to print the common prefix and size to un-clutter the demonstration.

    // printf("long long | %5Iu | %"LEN"lld | %"LEN"lld\n", sizeof(long long), LLONG_MIN, LLONG_MAX);
    prefix("long long", sizeof(long long));
    printf("%"LEN"lld | %"LEN"lld\n", LLONG_MIN, LLONG_MAX);
    
  4. For printing info about fixed width types, I'd recommend to group them with the above signed, unsigned groups rather than establish a new table layout. Also call them "int8_t", "uint64_t", etc.

I would also be grateful for advice on any aspect of the code, suggestions for alternate approach or better ways of doing it.

  1. Invalid print specifier "%5Iu".
    This led to "warning: unknown conversion type character 'I' in format" on my Windows based compiler. The use of "%5Iu" may work well with some compliers as a valid extension, but that is a compiler issue and not an OS issue as suggested with "For Unix-based (non-Windows) systems, use zu instead of Iu". Yet since all sizeof() are small here, code could use %d and (int) sizeof(xxx) and avoid the z vs. I issue.

  2. The fixed point types should use the macros such as PRId8, PRIu64 found in <inttypes.h> rather than guess the printf specifier. @Roland Illig

    // printf("64 bit | %"LEN"lld | %"LEN"llu\n", INT64_MAX, UINT64_MAX);
    printf("64 bit | %"LEN PRId64 " | %"LEN PRIu64 "\n", INT64_MAX, UINT64_MAX);
    
  3. The use of #define LEN "20" for width is relying on integer types being 64 bits or less. Highly portable code would adjust this. It is not unreasonable to see an upcoming compiler use long long as 128 bit.

  4. char is not necessarily a signed type. Instead of char, signed char would have made more sense. Further, hhd is not necessarily the matching numeric specifier for char. Likely need conditional code to handle char well.

    // printf("char      | %5Iu | %"LEN"hhd | %"LEN"hhd\n", 
    //     sizeof(char), CHAR_MIN, CHAR_MAX);
    printf("signed char| %5Iu | %"LEN"hhd | %"LEN"hhd\n", 
        sizeof(signed char), SCHAR_MIN, SCHAR_MAX);
    
    #if CHAR_MAX == SCHAR_MAX
      .... print using hhd
    #else
      .... print using hhu
    #endif   
    
  5. For completeness I'd expect the min/max type of signed char, intmax_t, uintmax_t. _Bool, size_t, ptrdiff_t, intptr_t, uintptr are some other common candidates for consideration.

  6. For the rare platform where CHAR_BIT != 8, Instead of listing "bytes", perhaps use "bits" and sizeof(xxx)*CHAR_BIT. On such rare machines note that fixed width types like int8_t, etc. are individually optional.

  7. @JS1. Certain typo

    // printf("short   | %5Iu | %"LEN"hd | %"LEN"hd\n", sizeof(char), SHRT_MIN, SHRT_MAX);
    printf("short   | %5Iu | %"LEN"hd | %"LEN"hd\n", sizeof(short), SHRT_MIN, SHRT_MAX);
    

Putting most of these ideas together. Improvements possible - just to give OP ideas.

    #include <inttypes.h>
    #include <limits.h>
    #include <math.h>
    #include <stdio.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <string.h>

void print_line(int n) {
  const char line_char = '=';
  while (n-- > 0) {
    putchar(line_char);
  }
  putchar('\n');
}

void prefix(int w, const char *pre, size_t sz) {
  printf("%-*s | %4u | ", w, pre, (int)(sz * CHAR_BIT));
}

int main(void) {
  int type_width = (int) strlen("unsigned long long");
  int max_width = (int) ceil(log10((double) UINTMAX_MAX));
  int min_width = (int) ceil(log10(-1.0 * INTMAX_MIN)) + 1;

  int nn = printf("%-*s | Bits | %*s | %*s\n",
      type_width, "Type", max_width, "Maximum", min_width, "Minimum");
  print_line(nn - 1);
  prefix(type_width, "signed char", sizeof(signed char));
  printf("%*hhd | %*hhd\n", max_width, SCHAR_MAX, min_width, SCHAR_MIN);
#if CHAR_MAX == SCHAR_MAX
  prefix(type_width, "char", sizeof(char));
  printf("%*hhd | %*hhd\n", max_width, SCHAR_MAX, min_width, SCHAR_MIN);
#endif
  prefix(type_width, "short", sizeof(short));
  printf("%*hd | %*hd\n", max_width, SHRT_MAX, min_width, SHRT_MIN);
  prefix(type_width, "int", sizeof(int));
  printf("%*d | %*d\n", max_width, INT_MAX, min_width, INT_MIN);
  prefix(type_width, "long", sizeof(long));
  printf("%*ld | %*ld\n", max_width, LONG_MAX, min_width, LONG_MIN);
  prefix(type_width, "long long", sizeof(long long));
  printf("%*lld | %*lld\n", max_width, LLONG_MAX, min_width, LLONG_MIN);
  prefix(type_width, "intmax_t", sizeof(intmax_t));
  printf("%*jd | %*jd\n", max_width, INTMAX_MAX, min_width, INTMAX_MIN);

  prefix(type_width, "ptrdiff_t", sizeof(ptrdiff_t));
  printf("%*td | %*td\n", max_width, PTRDIFF_MAX, min_width, PTRDIFF_MIN);

  prefix(type_width, "int8_t", sizeof(int8_t));
  printf("%*" PRId8 " | %*" PRId8 "\n", max_width, INT8_MAX, min_width, INT8_MIN);
  prefix(type_width, "int16_t", sizeof(int16_t));
  printf("%*" PRId16 " | %*" PRId16 "\n", max_width, INT16_MAX, min_width, INT16_MIN);
  prefix(type_width, "int32_t", sizeof(int32_t));
  printf("%*" PRId32 " | %*" PRId32 "\n", max_width, INT32_MAX, min_width, INT32_MIN);
  prefix(type_width, "int64_t", sizeof(int64_t));
  printf("%*" PRId64 " | %*" PRId64 "\n", max_width, INT64_MAX, min_width, INT64_MIN);

  prefix(type_width, "intptr_t", sizeof(intptr_t));
  printf("%*" PRIdPTR " | %*" PRIdPTR "\n", max_width, INTPTR_MAX, min_width, INTPTR_MIN);

  puts("");
  nn = printf("%-*s | Bits | %*s\n", type_width, "Type", max_width, "Maximum");
  print_line(nn - 1);
#if CHAR_MAX == UCHAR_MAX
  prefix(type_width, "char", sizeof(char));
  printf("%*hhu\n", max_width, UCHAR_MAX);
#endif
  prefix(type_width, "unsigned char", sizeof(unsigned char));
  printf("%*hhu\n", max_width, UCHAR_MAX);
  prefix(type_width, "unsigned short", sizeof(unsigned short));
  printf("%*hu\n", max_width, USHRT_MAX);
  prefix(type_width, "unsigned", sizeof(unsigned));
  printf("%*u\n", max_width, UINT_MAX);
  prefix(type_width, "unsigned long", sizeof(unsigned long));
  printf("%*lu\n", max_width, ULONG_MAX);
  prefix(type_width, "unsigned long long", sizeof(unsigned long long));
  printf("%*llu\n", max_width, ULLONG_MAX);
  prefix(type_width, "uintmax_t", sizeof(uintmax_t));
  printf("%*ju\n", max_width, UINTMAX_MAX);

  prefix(type_width, "size_t", sizeof(size_t));
  printf("%*zu\n", max_width, SIZE_MAX);

  prefix(type_width, "uint8_t", sizeof(uint8_t));
  printf("%*" PRIu8 "\n", max_width, UINT8_MAX);
  prefix(type_width, "uint16_t", sizeof(uint16_t));
  printf("%*" PRIu16 "\n", max_width, UINT16_MAX);
  prefix(type_width, "uint32_t", sizeof(uint32_t));
  printf("%*" PRIu32 "\n", max_width, UINT32_MAX);
  prefix(type_width, "uint64_t", sizeof(uint64_t));
  printf("%*" PRIu64 "\n", max_width, UINT64_MAX);

  prefix(type_width, "uintptr_t", sizeof(uintptr_t));
  printf("%*" PRIuPTR "\n", max_width, UINTPTR_MAX);

  return EXIT_SUCCESS;
}

Output

Type               | Bits |              Maximum |              Minimum
=======================================================================
signed char        |    8 |                  127 |                 -128
char               |    8 |                  127 |                 -128
short              |   16 |                32767 |               -32768
int                |   32 |           2147483647 |          -2147483648
long               |   32 |           2147483647 |          -2147483648
long long          |   64 |  9223372036854775807 | -9223372036854775808
intmax_t           |   64 |  9223372036854775807 | -9223372036854775808
ptrdiff_t          |   32 |           2147483647 |          -2147483648
int8_t             |    8 |                  127 |                 -128
int16_t            |   16 |                32767 |               -32768
int32_t            |   32 |           2147483647 |          -2147483648
int64_t            |   64 |  9223372036854775807 | -9223372036854775808
intptr_t           |   32 |           2147483647 |          -2147483648

Type               | Bits |              Maximum
================================================
unsigned char      |    8 |                  255
unsigned short     |   16 |                65535
unsigned           |   32 |           4294967295
unsigned long      |   32 |           4294967295
unsigned long long |   64 | 18446744073709551615
uintmax_t          |   64 | 18446744073709551615
size_t             |   32 |           4294967295
uint8_t            |    8 |                  255
uint16_t           |   16 |                65535
uint32_t           |   32 |           4294967295
uint64_t           |   64 | 18446744073709551615
uintptr_t          |   32 |           4294967295
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.