14

To print a number of type off_t it was recommended to use the following piece of code:

off_t a;
printf("%llu\n", (unsigned long long)a);
  • Why the format string is not enough?
  • What will be the problem if it were not casted?
Roman Byshko
  • 8,591
  • 7
  • 35
  • 57

3 Answers3

16

The format string doesn't tell the compiler to perform a cast to unsigned long long, it just tells printf that it's going to receive an unsigned long long. If you pass in something that's not an unsigned long long (which off_t might not be), then printf will simply misinterpret it, with surprising results.

The reason for this is that the compiler doesn't have to know anything about format strings. A good compiler will give you a warning message if you write printf("%d", 3.0), but what can a compiler do if you write printf(s, 3.0), with s being a string determined dynamically at run-time?


Edited to add: As Keith Thompson points out in the comments below, there are many places where the compiler can perform this sort of implicit conversion. printf is rather exceptional, in being one case where it can't. But if you declare a function to accept an unsigned long long, then the compiler will perform the conversion:

#include <stdio.h>
#include <sys/types.h>


int print_llu(unsigned long long ull)
{
  return printf("%llu\n", ull); // O.K.; already converted
}


int main()
{
  off_t a;

  printf("%llu\n", a); // WRONG! Undefined behavior!
  printf("%llu\n", (unsigned long long) a); // O.K.; explicit conversion
  print_llu((unsigned long long) a); // O.K.; explicit conversion
  print_llu(a); // O.K.; implicit conversion

  return 0;
}

The reason for this is that printf is declared as int printf(const char *format, ...), where the ... is a "variadic" or "variable-arguments" notation, telling the compiler that it can accept any number and types of arguments after the format. (Obviously printf can't really accept any number and types of arguments: it can only accept the number and types that you tell it to, using format. But the compiler doesn't know anything about that; it's left to the programmer to handle it.)

Even with ..., the compiler does do some implicit conversions, such as promoting char to int and float to double. But these conversions are not specific to printf, and they do not, and cannot, depend on the format string.

ruakh
  • 175,680
  • 26
  • 273
  • 307
  • 1
    Note that for a non-variadic function (or for any of the fixed arguments to a variadic function), the compile knows the expected type and will implicitly convert the argument if possible -- as long as a correct prototype is visible. For `printf("%f\n", 3)`, no conversion is done, and the call's behavior is undefined. For `sqrt(3)`, the `int` argument is implicitly converted to `double`. – Keith Thompson Nov 29 '11 at 00:10
  • @KeithThompson: Yes, that's a very good point, thanks. I've expanded my answer to cover that somewhat. (I didn't bother with the "as long as a correct prototype is visible" part, though, since I think that's getting a bit far from the topic. But if you think I should add it, I will.) – ruakh Nov 29 '11 at 00:44
  • 1
    One case where it matters is when you have a declaration that's not a prototype (i.e., an old-style function declaration that doesn't specify the types of the arguments); in that case, the same argument promotions are done as for variadic arguments. This is still permitted in C99. (Solution: Always use prototypes, never use old-style declarations.) Another point: Not all conversions that can be done with explicit casts are done implicitly for arguments. In particular, pointer conversions require (explicit) casts (other than the special case of a null pointer constant). – Keith Thompson Nov 29 '11 at 06:43
  • @KeithThompson: Definitely, but unprototyped functions are (hopefully) uncommon these days; and the fact that not all explicit casts can be done implicitly isn't specific to function calls, but also applies to assignment statements. – ruakh Nov 29 '11 at 15:27
  • @KeithThompson Sir , what is the difference between cast and implicit conversion. Doesn't cast means just interpreting the data and not changing the underlying bit pattern.If i write `(int) x` I am performing explicit conversion.On the other hand If i happen to write `int x = 2` `float y` `float z = 2.3` and then `y = x + z` . `x` is implicitly converted to `float` and then added to `z`. Now coming to casting if i have `int x = 2` and i write `printf("%f" , x);` Then `printf` will not perform implicit conversion it will just reinterpret the bits as it were representing `float`. – Suraj Jain Aug 22 '16 at 19:43
  • 1
    @SurajJain: A *cast* is an operation specified by a cast operator, a parenthesized type name: `(double)42`. It is an explicit conversion. An implicit conversion is simply a conversion that is not specified by a cast operator. There is no such thing as an "implicit cast" (though the phrase is often misused that way). – Keith Thompson Aug 22 '16 at 19:44
  • @KeithThompson Oh ok , sir then what do we say to just re-interpretation of a bit-pattern and not changing the bit pattern itself. – Suraj Jain Aug 22 '16 at 19:47
  • And i am right here that implicit conversion ---->coercion . Explicit Conversion ----> cast – Suraj Jain Aug 22 '16 at 19:47
  • @SurajJain: What about it? That's sometimes called "type punning". You can refer to implicit conversion as "coercion" if you like, but the C standard doesn't use that word and there's no strict definition of it. (No need to call me "sir".) – Keith Thompson Aug 22 '16 at 19:49
  • @KeithThompson And sir , printf cannot perform conversion , then how does `char` changes to `int` and `float` to `double` . And what is the difference between promotion and conversion ? – Suraj Jain Aug 22 '16 at 19:49
  • @KeithThompson Oh ok . So printf does type punning.?? – Suraj Jain Aug 22 '16 at 19:50
  • @KeithThompson Explicit Conversion is Casting . They Can be used interchangeably is that correct ?? – Suraj Jain Aug 22 '16 at 19:52
  • 1
    @SurajJain: You should probably find a good C reference or tutorial. Asking multiple questions in comments on somebody else's post is not a good way to learn the language. There are rules for type promotion that apply to variadic functions like `printf`; `float` is implicitly converted to `double`, and integer types narrower than `int` are implicitly converted to `int` or `unsigned int`. If the (promoted) type of an argument isn't what the format string requires, the behavior is undefined. The (nearly) definitive reference is http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf – Keith Thompson Aug 22 '16 at 19:54
  • @KeithThompson Ok . Thanks – Suraj Jain Aug 22 '16 at 19:55
5

The problem is you don't know how big an off_t is. It could be a 64 bit type or a 32 bit type (or perhaps something else). If you use %llu, and do not pass an (unsigned) long long type, you'll get undefined behavior, in practice it might just print garbage.

Not knowing how big it is, the easy way out is to cast it to the biggest reasonable type your system supports, e.g. a unsigned long long. That way using %llu is safe, as printf will receive an unsigned long long type because of the cast.

(e.g. on linux, the size of an off_t is 32 bit by default on a 32 bit machine, and 64 bit if you enable large file support via #define _FILE_OFFSET_BITS=64 before including the relevant system headers)

nos
  • 223,662
  • 58
  • 417
  • 506
2

The signature of printf looks like this:

int printf(const char *format, ...);

The vararg... indicates that anything can follow, and by the rules of C, you can pass anything to printf as long as you include a format string. C simply does not have any constructs to describe any restrictions for the types of objects passed. This is why you must use casts so that the objects passed have exactly the needed type.

This is typical for C, it walks a line between rigidity and trusting the programmer. An unrelated example is that you may use char * (without const) to refer to string literals, but if you modify them, your program may crash.

u0b34a0f6ae
  • 48,117
  • 14
  • 92
  • 101