Why is (void **) not compatible with (foo **) ?

Discussion in 'C Programming' started by Noob, Nov 8, 2012.

  1. Noob

    Noob Guest

    Hello,

    I know (void **) is not "compatible" with (foo **) but I'm wondering why.

    I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;

    Why is a pointer to v not be compatible with a pointer to i?

    Case in point, I'm using a library where (for consistency) every
    function returns an error code, even the memory allocation function.

    So I'm dealing with
    int mem_alloc(size_t size, void **p);

    Using malloc, I can just write
    foo *pf = malloc(sizeof *pf);

    but with this library, I can't write
    foo *pf;
    int err = mem_alloc(sizeof *pf, &p);

    I have to write
    foo *pf; void *pv;
    int err = mem_alloc(sizeof *pf, &pv);
    pf = pv;

    Or is there a better, simpler, clearer idiom?

    Regards.
     
    Noob, Nov 8, 2012
    #1
    1. Advertising

  2. On 11/8/12 5:08 AM, Noob wrote:
    > Hello,
    >
    > I know (void **) is not "compatible" with (foo **) but I'm wondering why.
    >
    > I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;
    >
    > Why is a pointer to v not be compatible with a pointer to i?
    >
    > Case in point, I'm using a library where (for consistency) every
    > function returns an error code, even the memory allocation function.
    >
    > So I'm dealing with
    > int mem_alloc(size_t size, void **p);
    >
    > Using malloc, I can just write
    > foo *pf = malloc(sizeof *pf);
    >
    > but with this library, I can't write
    > foo *pf;
    > int err = mem_alloc(sizeof *pf, &p);
    >
    > I have to write
    > foo *pf; void *pv;
    > int err = mem_alloc(sizeof *pf, &pv);
    > pf = pv;
    >
    > Or is there a better, simpler, clearer idiom?
    >
    > Regards.
    >


    The biggest part of the issue is that pointers to different types may be
    represented differently. In particular, a char* pointer may need
    additional information on a machine which uses "word" addresses to
    indicate which byte in the word to access.

    So while you can write a conversion from any type to/from void, this
    conversion is not necessarily "trivial". It is true that for most
    machines the pointers have the same representation, the standard is
    written to allow it to be implemented on a wide variety of machines.
     
    Richard Damon, Nov 8, 2012
    #2
    1. Advertising

  3. Noob

    James Kuyper Guest

    On 11/08/2012 05:08 AM, Noob wrote:
    > Hello,
    >
    > I know (void **) is not "compatible" with (foo **) but I'm wondering why.
    >
    > I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;
    >
    > Why is a pointer to v not be compatible with a pointer to i?


    There's two ways to answer that, depending upon what you mean by the
    "Why". First of all, there's the applicable citations from the standard:
    "For two pointer types to be compatible, both shall be identically
    qualified and both shall be pointers to compatible types." (6.7.6.1p2)
    Because there's two levels of indirection involved here, that rule gets
    applied twice.
    Since void and foo are not compatible types (I assume that foo is NOT a
    typedef for void), then void* and foo* are not compatible types. If
    void* and foo* are not compatible types, void** and foo** are not
    compatible types.

    The second way to answer that is in terms of the reason why that rule
    was written that way. Consider a machine with 16 bit addresses and a
    word size of 32 bits. This is just an example chosen to clarify the
    reasons behind that decision; machines with precisely that combination
    of characteristics (which are probably a bit unusual) are NOT the reason
    why this rule was adopted.

    C requires that char* pointers uniquely identify bytes. One option would
    be to choose CHAR_BIT==32, but a more conventional approach would be to
    choose CHAR_BIT==8, and to access char* using pointers that are more
    than 16 bits long. 16 of those bits would identify the particular word
    containing the char, and 2 additional bits would be used to identify
    which particular byte within that word contains the char. On such an
    implementation, 3 bytes would be the minimum size of such a pointer, but
    it would probably be rounded up to 4 bytes == 32 bits. Some such scheme
    must be used for any object that has a size less than the word size -
    let's call that the large pointer format. However, if larger types are
    always aligned on word boundaries, pointers to those types only need to
    contain 16-bit addresses - let's call that the small pointer format.
    Problems like this one are the main reason that the standard allows
    pointers to different types to have different representations, sizes,
    and alignment requirements. This particular problem only requires
    pointers of two different formats, but the standard allows an
    arbitrarily large number of mutually incompatible pointer formats.

    However, the standard does require that a void* have the same
    representation and alignment requirements as a char*, so on such a
    machine, it must have the large format. On the other hand, if
    sizeof(foo)>4, foo* could use the small format.
    OK, now let's apply that one more time: since sizeof(void*) == 4, void**
    can use the small pointer format. However, if sizeof(foo*)==2, then
    foo** must use the large pointer format. Those two formats are
    inherently incompatible.
    --
    James Kuyper
     
    James Kuyper, Nov 8, 2012
    #3
  4. 在 2012å¹´11月8日星期四UTC+8下åˆ6æ—¶08分20秒,Noob写é“:
    > Hello,
    >
    >
    >
    > I know (void **) is not "compatible" with (foo **) but I'm wondering why.
    >
    >
    >
    > I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;
    >
    >
    >
    > Why is a pointer to v not be compatible with a pointer to i?
    >
    >
    >
    > Case in point, I'm using a library where (for consistency) every
    >
    > function returns an error code, even the memory allocation function.
    >
    >
    >
    > So I'm dealing with
    >
    > int mem_alloc(size_t size, void **p);
    >
    >
    >
    > Using malloc, I can just write
    >
    > foo *pf = malloc(sizeof *pf);
    >
    >
    >
    > but with this library, I can't write
    >
    > foo *pf;
    >
    > int err = mem_alloc(sizeof *pf, &p);
    >
    >
    >
    > I have to write
    >
    > foo *pf; void *pv;
    >
    > int err = mem_alloc(sizeof *pf, &pv);
    >
    > pf = pv;
    >
    >
    >
    > Or is there a better, simpler, clearer idiom?
    >
    >
    >
    > Regards.


    I recommend you to read <Expert C Programming>, first chapter explain it
     
    楚蛮人, Nov 8, 2012
    #4
  5. Noob <root@127.0.0.1> writes:

    > I know (void **) is not "compatible" with (foo **) but I'm wondering
    > why.


    The reason is that C allows different pointer types to use different
    representations (though there are some restrictions). Take, for
    example, a word addressed machine. The efficient representation of,
    say, int * is as a word address. void * and char * must still be
    representable so let's imagine the implementation chooses to shift the
    word address to the left and to use the bottom bits (let's say two of
    them) for a byte offset.

    With this implementation, a pointer conversion will often generate
    code, but if foo ** and void ** were compatible, the required conversion
    can't be done:

    int x = 42;
    int *ip = &x; // let's say ip is the word address 0x100
    void *vp = ip; // vp is now the "invented" address 0x400

    int **ipp = &ip;
    void **vpp = ipp; // NOT PERMITTED -- let's pretend
    void *vp2 = *vpp; // what's in vp2? It must be 0x100
    assert(vp2 == vp1); // oops!

    > I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;
    >
    > Why is a pointer to v not be compatible with a pointer to i?
    >
    > Case in point, I'm using a library where (for consistency) every
    > function returns an error code, even the memory allocation function.
    >
    > So I'm dealing with
    > int mem_alloc(size_t size, void **p);
    >
    > Using malloc, I can just write
    > foo *pf = malloc(sizeof *pf);
    >
    > but with this library, I can't write
    > foo *pf;
    > int err = mem_alloc(sizeof *pf, &p);
    >
    > I have to write
    > foo *pf; void *pv;
    > int err = mem_alloc(sizeof *pf, &pv);
    > pf = pv;
    >
    > Or is there a better, simpler, clearer idiom?


    None comes to mind. If you try a cast

    foo *pf;
    int err = mem_alloc(sizeof *pf, (void **)&pf);

    you are "lying to the compiler". It will work on many implementations,
    but it brings up the hair on the back of my neck because I once had to
    port various Unix utilities to a word addressed machine. The sort
    program was full of code like that. I ended up using marker pens on a
    full code listing, colouring in what contained a word pointer and what
    contained byte pointer. It was horrible.

    --
    Ben.
     
    Ben Bacarisse, Nov 8, 2012
    #5
  6. Noob <root@127.0.0.1> writes:
    > Hello,
    > I know (void **) is not "compatible" with (foo **) but I'm wondering why.
    >
    > I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;
    >
    > Why is a pointer to v not be compatible with a pointer to i?


    Perhaps a better question would be, why *should* they be compatible?

    Type "compatibility", as defined by the standard, is a more stringent
    concept than assignability. For example, all arithmetic types are
    assignable to each other (with implicit conversions where necessary),
    but no two distinct arithmetic types are compatible, even if they
    happen to have the same representation.

    Pointers to distinct types are not compatible. void* and foo*
    are distinct types (even though they're assignable, due to special
    rules for void*), so pointers to them are incompatible.

    If you're actually wondering about assignability rather than
    compatibility, in general foo* and bar* are not assignable; there
    is no implicit conversion between foo* and bar* (unless either foo
    or bar is void, or foo and bar are compatible). If there were,
    then you could do things like this:

    foo foo_obj;
    foo *foo_ptr = &foo_obj;
    bar *bar_ptr = foo_ptr; /* invalid */

    which would allow to treat foo_obj as if it were an object of
    type bar. You *can* do type-punning like this, but it requires an
    explicit pointer cast (or a union, or memcpy() on a pointer object,
    or ...).

    The inability to assign a void** to a foo**, or vice versa, is just
    one case of this, where the targeted object types are void* and foo*.
    And in particular, it's plausible that a void* could be bigger than
    a foo*, or that it could have a different representation; in that
    case type-punning would cause serious problems. (On most modern
    implementations, all pointers have the same size and representation,
    but the C standard is careful not to assume that.)

    void* is (more or less) a generic pointer-to-object type. void**
    is *not* a generic pointer-to-pointer-to-object type, and in fact
    there is no generic pointer-to-pointer-to-object type.

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    Will write code for food.
    "We must do something. This is something. Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
     
    Keith Thompson, Nov 8, 2012
    #6
  7. Noob

    Old Wolf Guest

    On Nov 8, 11:08 pm, Noob <r...@127.0.0.1> wrote:
    > I know (void **) is not "compatible" with (foo **) but I'm wondering why.


    (void *) and (int *) might be different formats, e.g. I have
    programmed on a system where (int *) is 2 bytes and (void *)
    is 3 bytes. If you passed a pointer to (int *) to this function
    which writes 3 bytes, it'd be a buffer overflow (amongst other
    problems).
     
    Old Wolf, Nov 11, 2012
    #7
  8. On 2012-11-08 02:08, Noob wrote:
    > Hello,
    >
    > I know (void **) is not "compatible" with (foo **) but I'm wondering why.
    >
    > I can write void *pv = 0; int *pi = 0; pv = pi; pi = pv;
    >
    > Why is a pointer to v not be compatible with a pointer to i?


    Others have given good practical reasons regarding different
    representations of differently-typed pointers. More generally,
    allowing such a conversion would allow a hole through which
    type safety could be compromised.

    // assume A and B are unrelated types
    A a, *pa;
    B b;
    void** ppv = &pa; // assign A** to void** (illegal)
    *ppv = &b; // assign B* to void*

    In this example, pa is supposed to hold only pointers to A objects,
    but ppv pointing to pa allows assigning &b to pa, even without a cast.

    >
    > Case in point, I'm using a library where (for consistency) every
    > function returns an error code, even the memory allocation function.
    >
    > So I'm dealing with
    > int mem_alloc(size_t size, void **p);
    >
    > Using malloc, I can just write
    > foo *pf = malloc(sizeof *pf);
    >
    > but with this library, I can't write
    > foo *pf;
    > int err = mem_alloc(sizeof *pf, &p);
    >
    > I have to write
    > foo *pf; void *pv;
    > int err = mem_alloc(sizeof *pf, &pv);
    > pf = pv;
    >
    > Or is there a better, simpler, clearer idiom?


    I think the code is fine. Typically, you want to examine the error code
    before you actually use the pointer, so the code would look like this:

    void *pv;
    int err = mem_alloc(sizeof foo, &pv);
    if (err indicates an error) {
    // handle the error
    }
    else {
    foo *pf = pv;
    // use pf
    }

    Alternatively, you could make mem_alloc return the pointer (if
    it's feasible) and let the compiler take care of the conversion:

    void *mem_alloc(size_t size, int& err);

    int err;
    foo *pf = mem_alloc(size_t size, &err);

    (You can write a wrapper function if you cannot modify the library.)

    --
    Seungbeom Kim
     
    Seungbeom Kim, Nov 12, 2012
    #8
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    3
    Views:
    404
    John Roth
    Jul 29, 2005
  2. Mr. SweatyFinger
    Replies:
    2
    Views:
    2,250
    Smokey Grindel
    Dec 2, 2006
  3. Kristian Domke
    Replies:
    11
    Views:
    535
    George Sakkis
    Jan 23, 2008
  4. .rhavin grobert

    vector: Foo[5] == ((foo*)Foo) + 5 ?

    .rhavin grobert, Sep 23, 2008, in forum: C++
    Replies:
    4
    Views:
    419
    JaredGrubb
    Sep 24, 2008
  5. Replies:
    4
    Views:
    170
    Thomas 'PointedEars' Lahn
    Dec 23, 2007
Loading...

Share This Page