Thread: Disadvantages of void pointers.

  1. #1
    Registered User
    Join Date
    Sep 2011
    Location
    Athens , Greece
    Posts
    357

    Disadvantages of void pointers.

    Hello to all.

    This is one of the drawbacks using generic pointers.
    "There is no way to detect an error caused by pushing a pointer of the wrong type."

    Is there any code example to see???? I didn't find something from google.

    Thank you in advance

  2. #2
    Registered User
    Join Date
    May 2010
    Posts
    120
    I assume that since the void * doesn't enforce type, it might be hard to keep track of errors where you accidentaly pass a void pointer pointing to the wrong type of data.

    For instance:
    Code:
    typedef struct Test Test;
    struct Test
    {
        int a, b;
    };
    
    int initTest(void *vp)
    {
        Test *t = (Test *)vp;
        t->a = 20;
        t->b = 10; //crash
    }
    
    int main()
    {
        int a;
        void *vp = &a;
        initTest(vp);
        return 1;
    }
    This is perfectly valid code, yet the program will crash inside the function initTest() because the second integer it tries to reference is out of bounds.

  3. #3
    Registered User
    Join Date
    Sep 2011
    Location
    Athens , Greece
    Posts
    357
    The first integer is the a from main not the a from the structure. So the b integer in the call of initTest is not a known variable? it has nothing to do with the b variable inside the structure Test?

    also the return type of initTest is int but you return nothing.
    Last edited by Mr.Lnx; 04-09-2015 at 03:04 PM.

  4. #4
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    C doesn't enforce type safety on pointers of any kind. A particular C implementation may issue warnings for obvious stupidity, but they are not required to.
    What can this strange device be?
    When I touch it, it gives forth a sound
    It's got wires that vibrate and give music
    What can this thing be that I found?

  5. #5
    Registered User
    Join Date
    Sep 2007
    Posts
    1,012
    Quote Originally Posted by Elkvis View Post
    C doesn't enforce type safety on pointers of any kind. A particular C implementation may issue warnings for obvious stupidity, but they are not required to.
    Given two types T1 and T2, compilers are required to issue diagnostics (i.e. warning/error messages) if you assign T1* to T2* if T1 and T2 are not compatible types. Without going into smaller details such as type qualifiers, function types, etc, two types are considered compatible if they are the same type.

    So a compiler must issue a diagnostic for:
    Code:
    int *x = NULL;
    long *z = x;
    void* is a “hole” in the type system, because you can assign most pointer types to/from void* without a diagnostic. Casting is another such hole, since you can cast from (say) long* to int*, and the compiler doesn't have to issue a diagnostic.

    C's type system is pretty weak, because you can work around it in a number of ways, but it's not useless.

  6. #6
    Registered User
    Join Date
    Sep 2011
    Location
    Athens , Greece
    Posts
    357
    Ok I got it.

    According to shiroaisu's example vp points to an integer variable. But inside the function *t is a pointer to a structure. So there is no way to check this inconsistency.

    t->a is weird to me for this case though. Can you assign a value with this way to integer that is not member of a structure?

  7. #7
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,616
    Quote Originally Posted by Mr.Lnx View Post
    t->a is weird to me for this case though. Can you assign a value with this way to integer that is not member of a structure?
    It should seem weird. It isn't normal to try and hack C's type system. But when you get down to how C really works, there isn't much stopping you from assigning to an int by pretending it's a structure.

    C99 requires that objects be represented by a contiguous sequence of bytes.
    Quote Originally Posted by section 6.2.6.1 paragraph 2
    Except for bit-fields, objects are composed of contiguous sequences of one or more bytes,
    the number, order, and encoding of which are either explicitly specified or
    implementation-defined.
    Further down, the standard says:
    4 Values stored in non-bit-field objects of any other object type consist of n x CHAR_BIT
    bits, where n is the size of an object of that type, in bytes. The value may be copied into
    an object of type unsigned char [n] (e.g., by memcpy); the resulting set of bytes is
    called the object representation of the value. [...]
    This makes it possible to do all sorts of wonderful things. For instance, any object can be punned into a sequence of unsigned chars -- (unsigned char*)&obj -- this is the reason that memcpy can work.

    5 Certain object representations need not represent a value of the object type. If the stored
    value of an object has such a representation and is read by an lvalue expression that does
    not have character type, the behavior is undefined. If such a representation is produced
    by a side effect that modifies all or any part of the object by an lvalue expression that
    does not have character type, the behavior is undefined.41) Such a representation is called
    a trap representation.

    6 When a value is stored in an object of structure or union type, including in a member
    object, the bytes of the object representation that correspond to any padding bytes take
    unspecified values.42) The value of a structure or union object is never a trap
    representation, even though the value of a member of the structure or union object may be
    a trap representation.

    41) Thus, an automatic variable can be initialized to a trap representation without causing undefined
    behavior, but the value of the variable cannot be used until a proper value is stored in it.

    42) Thus, for example, structure assignment need not copy any padding bits.
    Let's simplify shiroaisu's example for discussion:
    Code:
    struct T {
        int a;
    };
    
    void foo(void *pointer)
    {
        struct T *t = pointer;
        t->a = 10;
    }
    
    int main()
    {
        int x = 1;
        foo(&x);
    }
    Now, does the program exhibit undefined behavior or not? It does. Since the object representations of an integer and a struct T containing an integer do not have to be the same, there are a bunch of question marks around this. The program can appear to work correctly when the compiler decides that the way to represent the structure T is the same way to represent other integers. (After all, where exactly is the difference in bits.) However, it is also valid to allocate space for the integer and then some padding. If you're lucky, the computer would segfault in that case. A standards conforming compiler could try to access the padding, which would probably cause a crash.

    The bad thing about undefined behavior is that tons of programs invoking it appear to work. Crashes are a good thing, a sign something is probably a mistake.

    In the same vein, void pointers/casts are bad, for silently allowing things like this.
    Last edited by whiteflags; 04-09-2015 at 07:53 PM.

  8. #8
    Registered User
    Join Date
    Mar 2012
    Location
    the c - side
    Posts
    373
    Something not mentioned is that a void pointer does carry some type information, namely it will contain an address which is generally memory aligned for the object type it points to.

    And in general a pointer should be memory aligned, by holding an address which is a multiple of the size of the type it is pointing to.

    If you convert a pointer from one type to another and the resulting pointer is not memory aligned - that is undefined behaviour.

    Here, as Whiteflags has mentioned, we can't be strictly sure of the size of the struct which means there is no way of knowing that the resulting pointer is memory aligned.

    So that would indicate to me that:

    Code:
     
    
    Test *t = (Test *)vp;
    And,

    Code:
    struct T *t = pointer;
    both exhibit undefined behaviour.
    Last edited by gemera; 04-10-2015 at 12:29 AM.

  9. #9
    Registered User
    Join Date
    Mar 2012
    Location
    the c - side
    Posts
    373
    Unless there is a flaw in my argument?, it would mean that more generally - the conversion of a pointer to any primitive type to a pointer to a struct type results in undefined behaviour.

  10. #10
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by gemera View Post
    Unless there is a flaw in my argument?, it would mean that more generally - the conversion of a pointer to any primitive type to a pointer to a struct type results in undefined behaviour.
    No, the conversion of a pointer to any primitive type to a pointer to a struct type does not necessarily result in undefined behaviour.

    In fact, in C99, if the first member of that struct is of that primitive type, or of type char (which is required to have size 1 and alignment 1), the conversion itself does not result in undefined behaviour.

    Let me show you the references.

    Quote Originally Posted by C99 6.3.2.3 p 7
    A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined.
    In other words, the conversion is undefined behaviour only if the pointer is not sufficiently aligned. If the pointer is sufficiently aligned, there is no problem (in the conversion itself, at least).

    If the first element of the structure is of the primitive type (or if two structures are involved, their first elements are of the same type), the alignment will be sufficient.
    Quote Originally Posted by C99 6.7.2.1 p 13
    Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.
    For two structures with bitfields as first members, their units would have to be the same as well. The above says that a pointer to a structure must point to the first member in that structure, without any extra padding.




    Side note: This also means that if your linked list and tree node structures start with the pointers, i.e.
    Code:
    struct dlist {
        struct dlist *prev;
        struct dlist *next;
    };
    
    struct dlist_a {
        struct dlist_a *prev;
        struct dlist_a *next;
        double value;
    };
    
    struct dlist_b {
        struct dlist_b *prev;
        struct dlist_b *next;
        char data[];
    };
    the first two members will be compatible across all three structures, and you only need one set of functions to manipulate any doubly linked list (or whatever the structure is), as long as the pointers in the actually used type are also at the same order at the beginning of the structure.

    It is frigging annoying to see all pointer tutorials put the structure pointers last in the structure.

  11. #11
    Registered User
    Join Date
    May 2010
    Posts
    120
    If the first element of the structure is of the primitive type (or if two structures are involved, their first elements are of the same type), the alignment will be sufficient.
    But doesn't malloc automatically align memory blocks using the bigger type so that isn't a problem?

  12. #12
    Registered User
    Join Date
    May 2010
    Posts
    120
    Quote Originally Posted by shiroaisu View Post
    But doesn't malloc automatically align memory blocks using the bigger type so that isn't a problem?
    Disregard my stupid statement wich I can't seem to edit. I have one question though: how much did the standards on padding and pointers changed over the years? Will "the C language" still be a good source on this subject, or is it too outdated?

  13. #13
    Registered User
    Join Date
    Mar 2012
    Location
    the c - side
    Posts
    373
    Quote Originally Posted by Nominal Animal View Post
    If the first element of the structure is of the primitive type (or if two structures are involved, their first elements are of the same type), the alignment will be sufficient.
    What you are saying would be correct for a normal "suitably converted" pointer.

    By that I mean you have declared an actual structure variable and are then just creating a pointer to it.

    e.g. say the structure was 8 bytes and contained an int of 4 bytes and 4 bytes padding.

    Then the structure as I understand it would be aligned in terms of the 4 byte int and 8 byte structure.

    So its address would be divisible by 4 and 8.

    But in the examples we have been considering and for primitives in general as I see it you would definitely
    have a pointer aligned to the size of the primitive, but then you are forcing that alignment on the structure.

    So for example an arbitrary int address will be divisible by 4 but is not always divisble by 8 ( if we again
    assume an 8 byte structure). So basically there is no guarantee that the int alignment is compatible
    with the structure alignment.
    Last edited by gemera; 04-11-2015 at 12:14 AM.

  14. #14
    Registered User
    Join Date
    Mar 2012
    Location
    the c - side
    Posts
    373
    It's ok nominal - figured out where I went wrong.

    The struct as a whole isn't aligned by its size - misread something.

  15. #15
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by shiroaisu View Post
    But doesn't malloc automatically align memory blocks
    As you probably know, malloc() returns a pointer sufficiently aligned for any standard type.

    However, it does not apply to extended types -- extended as in extensions to C -- like __float128 or certain vector types, and that is truly problematic. (See GCC bug 55916 for real world issues related to it, and a workaround.)

    Quote Originally Posted by shiroaisu View Post
    I have one question though: how much did the standards on padding and pointers changed over the years? Will "the C language" still be a good source on this subject, or is it too outdated?
    I don't know. Mostly the latter standards either add new stuff (and new rules), or specify things that earlier versions left implementation-defined. (Sometimes implementation-defined behaviour has been changed to undefined behaviour, though.)

    I rely on the publicly available versions (or final drafts) of the standards: C89 (ANSI C == C89 == C90), C99, C11, as linked to from the ANSI C, C99, and C11 (C standard revision) Wikipedia pages.

    I do know that structure padding has not changed: See ANSI C 3.5.2.1, C99 6.7.2.1, C11 6.7.2.1. With regards to structure padding, they say the same thing: the pointer to a structure points to the first member; there cannot be any padding prior to the first member. (Same applies to unions, too: the pointer to an union points to the first member.)

    Pointers have had one major change, related to the concept of pointer aliasing (strict aliasing). C99 added the restrict keyword.

    Note that regardless of the C standards, there exists a lot of code that relies on specific implementation-defined behaviour. One example is interpreting the storage representation of one type as another type using an union -- for example, using an unsigned int or unsigned long member to examine the bit pattern of a float or double member. In C89, that was implementation-defined behaviour, but a lot of code expects it to work. In C99 and later, it is undefined behaviour. (You should use memcpy()/memmove() for this, with either an unsigned char array, or a specific-size unsigned integer type that matches the size of the other type.)

    Quote Originally Posted by gemera View Post
    It's ok nominal - figured out where I went wrong.
    No worries!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Void pointers
    By see the big C in forum C Programming
    Replies: 6
    Last Post: 06-16-2010, 03:32 PM
  2. void* pointers
    By Drogin in forum C Programming
    Replies: 6
    Last Post: 09-04-2009, 11:04 AM
  3. void pointers
    By na_renu in forum C Programming
    Replies: 1
    Last Post: 10-21-2007, 11:34 AM
  4. void pointers
    By jverkoey in forum C++ Programming
    Replies: 2
    Last Post: 08-13-2003, 06:19 PM
  5. void pointers
    By coug2197 in forum C Programming
    Replies: 2
    Last Post: 11-30-2001, 02:41 PM