0

I don't know how to describe my problem, but here's the thing: I was trying to understand pointers better when reading K&R's book. I used their version of strlen() which used pointers, and that worked fine. Just to experiment with pointer arithmetic further, I wrote a really simple code:

#include <stdio.h>

int main(void)
{
    char s[10];
    char *sp;

    sp = s;

    printf("%d\n", (sp+7) - s);
    return 0;
}

It functioned correctly, but I don't understand the why this warning occurs:

hello3.c:10:17: warning: format specifies type 'int' but the argument has type 
      'long' [-Wformat]
        printf("%d\n", (sp+7) - s);
                ~~     ^~~~~~~~~~
                %ld

I understand what the error is, but I don't know why this happens. How did that expression became a long type?

HarryGeez
  • 63
  • 1
  • 1
  • 7
  • 3
    Don't use screenshots; copy-and-paste the text into your program. The code in your question doesn't produce that warning; the code in the screenshot does. Show us the code that produces the warning and the warning itself *in the question*. – Keith Thompson Aug 12 '14 at 16:50
  • pointers are `long` type (e.g. 32bit). But you're telling C to print them as an int (`%d`), which is a 16bit data type. `0x12340001` and `0x98760001` would both theoretically print out `0x0001` and appear identical, even though they're completely different values. – Marc B Aug 12 '14 at 16:50
  • 6
    @MarcB: No, pointers are not "`long` type". Pointers are pointers. The result of subtracting two pointers is of type `ptrdiff_t`, a typedef defined in `` and corresponding to some implementation-defined signed integer type. `int` is *at least* 16 buts, but is commonly wider. But since the code that produces the error is not in the question, I suggest we hold off on answering it. – Keith Thompson Aug 12 '14 at 16:52
  • The warning showed in the screenshot is on a piece of code that does not appear in your question!!! – barak manos Aug 12 '14 at 17:17
  • sorry guys fixed it. – HarryGeez Aug 12 '14 at 17:28
  • Related: [This answer](http://stackoverflow.com/a/19684560/509868) to a more specific question – anatolyg Aug 12 '14 at 17:33
  • I can't withdraw my close vote, but it's no longer unclear. I'd say it is, however, a duplicate of [this question](http://stackoverflow.com/q/7954439/827263). – Keith Thompson Aug 12 '14 at 18:25

2 Answers2

0

If the warning is on the return, in:

int istrlen(char *s)
{
  ...
  return p - s;
}

Then your compiler is being helpful... in this case, gcc will generate a warning if you ask for -Wconversion, because...

...the sum p - s is defined (C99 6.5.6), where both are pointers (to elements of the same array object, or to one past the last element of the same array object), to give a result of type ptrdiff_t -- which is a signed integer. Now, on a typical 64-bit processor your ptrdiff_t will be a 64-bit signed integer, and your int is a 32-bit signed integer. So, your compiler may be warning you that the conversion down from 64-bit to 32-bit signed integer, there could be an overflow.

You can write:

  return (int)(p - s) ;

to take responsibility for this, and the compiler will shrug and get on with its life.

[I'm slightly surprised that the warning is not given with straightforward -Wall... so this may not be the warning the question refers to.]

My second edition K&R is essentially C89, and also describes ptrdiff_t and its pal size_t. I suspect the fragment of code you are looking at comes from a kinder, gentler age when int and ptrdiff_t were interchangeable (32-bit integers) -- or compilers were less keen to suggest that the programmer may not know what they are doing. (Mind you, in 1989 a string longer than 2,147,483,647 characters was seriously unlikely !)


The other place where the compiler may be being helpful is at the:

  printf("%d\n", (sp+7) - s);

Where, again, the result of the subtraction is ptrdiff_t, but the %d expects an int. This is a more serious problem, because the %d only knows how to eat an int, and may go wrong eating an int, especially where there are more arguments following it.

  • That statement doesn't produce the reported warning, and adding a cast would not be helpful. The linked screenshot shows the actual code and the warning that it produces. The code currently in the question is largely irrelevant. The question needs to be updated. (It would probably be better to change `istrlen` so it returns `size_t` rather than `int`.) – Keith Thompson Aug 12 '14 at 17:20
  • @KiethThompson... on my gcc, `-Wconversion` generates a warning: "conversion to 'int' from 'long int' may alter its value". I confess, however, to being slightly surprised that `-Wall` and `-Wextra` are not sufficient ! –  Aug 12 '14 at 17:46
  • I had rather assumed that it was the `return s - p ;` which was generating a warning. Sorry. On more mature reflection, and with further information, the warning in question is for the same reason, but in a more significant place... I have updated the answer –  Aug 12 '14 at 18:01
0

Subtraction of two pointers yields a result of type ptrdiff_t, which is a typedef (alias) for some implementation-defined signed integer type.

You have:

printf("%d\n", (sp+7) - s);

where the operands of the subtraction are both pointers of type char* (s is an array which decays to a pointer to its first element). The "%d" format requires an argument of type int. ptrdiff_t is apparently a typedef for long on your system.

To fix this, either cast the result (probably to long, but int should work):

printf("%ld\n", (long)((sp+7) - s));

or use the correct format string for ptrdiff_t:

printf("%td\n", (sp+7) - s);

Personally, I'd probably use the cast; I didn't remember that t is the length modifier for ptrdiff_t until I looked it up in the standard. (And pre-C99 implementations might not support "%td".)

Passing printf an argument that's not of the correct type (after promotion) specified by the format string causes undefined behavior. As for why it happened to work, perhaps int and long happen to have the same size and representation in your implementation, or if long is wider than int perhaps long arguments are passed in such a way that when printf tries to grab an int value (probably off the stack) it happens to get the low-order sizeof (int) bytes of the long argument. That's the nature of undefined behavior; the worst thing that can happen is that it appears to work correctly. (That's bad because the code could fail unpredictably later on.)

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631