The usage of %p in C

Discussion in 'C Programming' started by Tak, Sep 25, 2007.

  1. [/QUOTE]
    Even this would not usually be a problem: provided there was a
    function prototype in scope the pointer would be converted to void *.
    You can perfectly well give an int * argument to free(), for example.
    Unfortunately, because printf() is a variadic function, the compiler
    can't tell what type is going to be expected, so it can't do the
    conversion.

    Some compilers do have special knowledge of printf(). Gcc in this
    case gives a warning:

    warning: format '%p' expects type 'void *', but argument 2 has type 'int *'

    -- Richard
     
    Richard Tobin, Sep 25, 2007
    #21
    1. Advertisements

  2. Tak

    Richard Guest

    I was. A question if you will.

    I'm a bit hazy at the moment but I remember someone harping on about
    pointers of one type being of a special type and residing in "special
    memory". I suspect it comes out in the wash in that the compiler could
    "convert" the "normal storage" to "special" storage unbeknown to the
    program.

    Then again, I could be meandering too much. I just had a peek
    at Facebook. Shockingly crass.

    Fair enough.
    Yes, I understand that. variadic functions weren't really the issue to
    my question or pondering.
     
    Richard, Sep 25, 2007
    #22
    1. Advertisements

  3. Tak

    Jack Klein Guest

    Actually there could be conforming implementations where this code
    could fail even if both pointer types have the same size. I think I
    might actually have used one, a quarter century or so ago, but I can't
    remember for sure.

    Same size does not necessarily guarantee passing and/or returning in
    the same manner (same register, same stack location, same memory
    block, whatever).

    Not quite the same thing, I do remember compilers for the Motorola
    68000 series (old Apple, Atari, Amiga computers) that would return
    integer types in a specific D register, pointers in one of the A
    registers.

    So failure to include a prototype for malloc() and using the cast:

    char *cp = (char *)malloc(some_size);

    ....would result in the pointer being initialized with some totally
    untreated value from a data register, completely ignoring the actual
    pointer in an address register.

    The point is, passing anything other than pointer to void to printf()
    to match a "%p" conversion specifier is undefined behavior. Even if
    it works on most compilers. Even if it works on all the compilers you
    have tried it on.

    There are reasons to write a program that uses implementation-defined,
    and even technically undefined behavior, if you know what your
    implementation does with them. Feeling like you can't be bothered to
    write the "(void *)" cast in one of the few situations in C where such
    a cast is not necessary is not one of the valid reasons.

    --
    Jack Klein
    Home: http://JK-Technology.Com
    FAQs for
    comp.lang.c http://c-faq.com/
    comp.lang.c++ http://www.parashift.com/c++-faq-lite/
    alt.comp.lang.learn.c-c++
    http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html
     
    Jack Klein, Sep 25, 2007
    #23
  4. Tak

    Jack Klein Guest

    Yes, that is correct.
    Well, that is almost correct. There is a guarantee with pointer to
    void with respect to pointer to any object type. There is no
    correspondence, defined conversion, or any other guarantee between
    pointers to objects and pointers to functions.

    Here is what the standard says:

    "A pointer to void may be converted to or from a pointer to any
    incomplete or object type. A pointer to any incomplete or object type
    may be converted to a pointer to void and back again; the result shall
    compare equal to the original pointer."
    If you read the quotation from the standard above, you will note that
    it requires that pointer to void be able to hold all the useful
    information from any other pointer to object type. A perverse
    implementation could have pointers to void and character types
    actually occupy less space than pointers to other object types,
    although those larger pointers to other types could not use more bits.
    There is nothing that requires that a compiler pass pointer to void
    and, for example, pointer to int, in the same manner to functions.
    printf() is a variadic function, where there is no prototype to tell
    the compiler to convert the parameters, if necessary.

    What happens if a particular compiler passes pointer to void in
    register R3 and pointer to int in register R5?

    Mainly, it is undefined behavior because the C standard says that it
    is.
    Again, 3) is not necessarily correct. A compiler for a platform with
    a 32-bit address space could have 32 bit pointers to void, and 64 bit
    pointers to int, although it could only use 32 bits of the pointer to
    int.
    --
    Jack Klein
    Home: http://JK-Technology.Com
    FAQs for
    comp.lang.c http://c-faq.com/
    comp.lang.c++ http://www.parashift.com/c++-faq-lite/
    alt.comp.lang.learn.c-c++
    http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html
     
    Jack Klein, Sep 25, 2007
    #24
  5. Tak

    Chris Torek Guest

    Any "data pointer", anyway. (That is, "void *" might not be able
    to hold a pointer-to-function type. This is the case in some MS-DOS
    compiler models, for instance, when functions use 32-bit addresses
    and "data bytes" live in a 16-bit address space.)
    Not necessarily -- it just means that "void *" values have enough
    data to *reconstruct* any *valid* data pointer.

    Imagine, for instance, that for some reason "int *" uses a megabyte,
    but only 39 bits of that megabyte really specify the actual memory
    address. Then "void *" might fit in a 21-byte object, with 39 bits
    of those 29 bytes holding the "important" 39 bits of the "int *".
    Note that a different subset of the "void *" bits might hold the
    "important" bits of "short *", and so on.

    (On Real Machines, of course, there are only three or four actual
    pointer formats, typically 4, 8, and 16 or so bytes each.)
    Perhaps "int *" values are passed in red registers, and "void *"
    values in blue registers. (Most integers go in green registers.
    Yellow registers are for float, purple for double, and the black
    registers are reserved to the secret part of the system. :) )
    This is up to the implementation -- the C Standard requires that
    the implementation achieve it *somehow*, though.
     
    Chris Torek, Sep 25, 2007
    #25
  6. Without the cast it's UB, period. If it happens to do what the programmer
    expects on systems where sizeof(int*) == sizeof(void*), that's one possible
    result of UB.

    S
     
    Stephen Sprunk, Sep 25, 2007
    #26
  7. No need to get malloc() and friends involved.

    Consider:

    char *;
    and
    char (*)(char);

    One points to a char, the other to a function that takes and returns
    a char. One points to data, the other to code. Who says that the
    way you point to data has any relation to how you point to code?

    First, you have the simple case of segmented memory architecture,
    where you may have 32-bit code pointers and 48-bit data pointers.

    Next, there are cases where the pointers may be the same size, but
    formatted differently. I once worked on a platform with 36-bit
    words and 18-bit addresses. Within a 36-bit word, you could hold
    a "pointer" to bits within an address. (ie: 18 bits for address,
    plus additional bits to point within the 36-bit value stored there.)
    While I never used a C compiler on that system, it is quite
    possible that a pointer to code would be a straight 18-bit value,
    with the other bits ignored, while a pointer to data would be the
    18-bit address plus the additional bits to point within the data.
    And yet, they would both be 36 bit values.

    --
    +-------------------------+--------------------+-----------------------+
    | Kenneth J. Brody | www.hvcomputer.com | #include |
    | kenbrody/at\spamcop.net | www.fptech.com | <std_disclaimer.h> |
    +-------------------------+--------------------+-----------------------+
    Don't e-mail me at: <mailto:>
     
    Kenneth Brody, Sep 25, 2007
    #27
  8. In fact the cast is strictly speaking *required*, precisely _because_
    you cannot guarantee that pointer sizes are the same.

    Obviously if you don't need to worry about portability and never
    expect to get a new compiler or OS, then....
    --
    Mark McIntyre

    "Debugging is twice as hard as writing the code in the first place.
    Therefore, if you write the code as cleverly as possible, you are,
    by definition, not smart enough to debug it."
    --Brian Kernighan
     
    Mark McIntyre, Sep 25, 2007
    #28
  9. You're probably thinking of MacOS or possibly TOS, where different
    types sometimes got passed in different registers.
    --
    Mark McIntyre

    "Debugging is twice as hard as writing the code in the first place.
    Therefore, if you write the code as cleverly as possible, you are,
    by definition, not smart enough to debug it."
    --Brian Kernighan
     
    Mark McIntyre, Sep 25, 2007
    #29
  10. [...]

    Different pointer types can have different representations, but
    malloc()ed memory can hold objects of any type. A conforming
    implementation can't restrict certain types of objects to be stored
    only in certain memory regions. (If such "special" objects are to be
    provided, the implementation can provide access to them, but not as
    ordinary C objects; there might be a compiler extension.)
     
    Keith Thompson, Sep 25, 2007
    #30
  11. [/QUOTE]
    I think that even if the pointer sizes are the same, and they have
    the same representation, nothing stops an implementation from passing
    int * and void * pointers to variadic functions in different ways.

    In fact, I don't see anything that stops it from passing void * and char *
    in different ways, which seems like a defect to me.

    -- Richard
     
    Richard Tobin, Sep 25, 2007
    #31
  12. void * and char * may be passed in different ways if and only if va_arg
    can still be used to access either as the other. If you use it to access
    a void * argument as were it a char *, or vice versa, you get the same
    value. There's a special exception in the definition of va_arg that makes
    this so.

    But yes, for printf you have to get the type right, since it need not be
    implemented using va_arg.
     
    =?iso-2022-kr?q?=1B=24=29CHarald_van_D=0E=29=26=0F, Sep 25, 2007
    #32
  13. Tak

    Army1987 Guest

    What am I missing which causes the last sentence not to be a
    tautology, so that "in all likelyhood" is needed instead of
    "obviously"?
     
    Army1987, Sep 25, 2007
    #33
  14. Tak

    Army1987 Guest

    You meant "...a cast is necessary..."?
     
    Army1987, Sep 25, 2007
    #34
  15. It depends on the meaning of "platform". It's possible, for example,
    that an operating system might have a convention of printing pointers
    in octal, but a C implementation running on that OS might print them
    in hexadecimal.

    If "platform" is synonymous with "implementation", then yes, it's a
    tautology.
     
    Keith Thompson, Sep 25, 2007
    #35
  16. Tak

    Old Wolf Guest

    It could be argued (and has been, in fact) that this
    causes undefined behaviour. The standard specifies that
    %x requires an unsigned int argument.

    I can't see anything other than a DS9000 failing it, though.
     
    Old Wolf, Sep 26, 2007
    #36
  17. On the other hand, even on a DS9000 the following:

    printf("hex -> %x\n", (unsigned)i);

    will work properly (unless there's an I/O error).

    If you're writing new code, it's easier to use exactly the right type
    in the first place than to prove that it's ok to use a slightly
    different type.

    But if you see the above in somebody else's program that isn't
    working, it should be safe to look elsewhere for the source of the
    bug.
     
    Keith Thompson, Sep 26, 2007
    #37
  18. Tak

    Richard Bos Guest

    [ Snip! ]
    Yes, they could; but those extra bits could not be used to hold required
    information, and a comparison of pointers that are equal, except that
    one has information in the extra bits and the other lost that extra
    information in passing through a void * and back, must compare equal.
    Two pointers which both have information in the extra bits are still
    allowed to compare different, AFAICT. (One reason for doing this is to
    provide debugging information. It may not be a _good_ reason to do so
    for normal pointers, but not for void * and char *; but it is a legal
    one.)

    Richard
     
    Richard Bos, Sep 26, 2007
    #38
  19. No, they are not. The same way as integers with different padding bits
    but the same value bits are required to compare equal.
    If some debug information were stored in those padding bits
    implementation would be forced to ignore them when comparing pointers.
    (Well, it is possible to create a pointer where those extra bits store
    some kind of CRC in which case implementation could use it while
    comparing pointers.)
     
    Michal Nazarewicz, Sep 26, 2007
    #39
  20. Tak

    Army1987 Guest

    <nitpick>
    If there's an I/O error it will return a negative value, which is
    the proper behavior in that case.
    </nitpick>
    :)
     
    Army1987, Sep 26, 2007
    #40
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.