type-punning?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • j.j.fishbat@gmail.com

    type-punning?

    Hi all

    I have a program which, with inessential details removed,
    looks like this:

    --
    #include <stdlib.h>
    #include <stdio.h>

    static int punme(void** dat,size_t newsize)
    {
    void *newdat = realloc(*dat,ne wsize);

    if (! newdat) return 1;

    *dat = newdat;

    return 0;
    }

    int main (void)
    {
    char *dat = malloc(30);

    int ret = punme((void**)& dat,40);

    printf("punme returns %i\n",ret);

    return 0;
    }
    --

    My compiler (gcc -Wall -O3) tells me that "typepun.c: 19:
    warning: dereferencing type-punned pointer will break
    strict-aliasing rules". The code works as expected, ie,
    prints that punme returns 0.

    Who is the idiot? Me or the compiler?

    Many thanks!

    Jim
  • vippstar@gmail.com

    #2
    Re: type-punning?

    On May 6, 6:46 pm, j.j.fish...@gma il.com wrote:
    Hi all
    >
    I have a program which, with inessential details removed,
    looks like this:
    >
    --
    #include <stdlib.h>
    #include <stdio.h>
    >
    static int punme(void** dat,size_t newsize)
    {
    void *newdat = realloc(*dat,ne wsize);
    >
    if (! newdat) return 1;
    >
    *dat = newdat;
    >
    return 0;
    >
    }
    >
    int main (void)
    {
    char *dat = malloc(30);
    >
    int ret = punme((void**)& dat,40);
    >
    printf("punme returns %i\n",ret);
    >
    return 0;}
    >
    --
    >
    My compiler (gcc -Wall -O3) tells me that "typepun.c: 19:
    warning: dereferencing type-punned pointer will break
    strict-aliasing rules". The code works as expected, ie,
    prints that punme returns 0.
    >
    Who is the idiot? Me or the compiler?
    You (cast) and assign a char ** to a void **. void ** is not like void
    *.
    Fixed your code:
    static int punme(void* dat,size_t newsize) {
    void **tmp = dat;
    void *newdat = realloc(*tmp, newsize);
    You also don't free the allocated memory.

    Comment

    • Harald van =?UTF-8?b?RMSzaw==?=

      #3
      Re: type-punning?

      On Tue, 06 May 2008 09:43:53 -0700, vippstar wrote:
      On May 6, 6:46 pm, j.j.fish...@gma il.com wrote:
      >#include <stdlib.h>
      >#include <stdio.h>
      >>
      >static int punme(void** dat,size_t newsize) {
      > void *newdat = realloc(*dat,ne wsize);
      >>
      > if (! newdat) return 1;
      >>
      > *dat = newdat;
      >>
      > return 0;
      >>
      >}
      >>
      >int main (void)
      >{
      > char *dat = malloc(30);
      >>
      > int ret = punme((void**)& dat,40);
      >>
      > printf("punme returns %i\n",ret);
      >>
      > return 0;}
      Heh, this looks familiar. :)
      >My compiler (gcc -Wall -O3) tells me that "typepun.c: 19: warning:
      >dereferencin g type-punned pointer will break strict-aliasing rules".
      >The code works as expected, ie, prints that punme returns 0.
      >>
      >Who is the idiot? Me or the compiler?
      You (cast) and assign a char ** to a void **. void ** is not like void
      *.
      Fixed your code:
      >static int punme(void* dat,size_t newsize) { void **tmp = dat;
      >void *newdat = realloc(*tmp, newsize);
      This doesn't fix the problem. This merely reorganises the code in a form
      that the compiler may happen to not warn about.

      The problem is that dat is defined as char *. You can't pretend it's
      defined as a void *. The language doesn't let you. A fixed version looks
      like

      #include <stdlib.h>
      #include <stdio.h>

      static void *punme(void *dat, size_t newsize) {
      return realloc(dat, newsize);
      }

      int main (void)
      {
      char *dat = malloc(30);
      char *newdat = punme(dat, 40); /* or call realloc directly */
      if (newdat != NULL) dat = newdat;
      printf("punme would have returned %d\n", (newdat == NULL ? 1 : 0));
      return 0;
      }

      Comment

      • Andrey Tarasevich

        #4
        Re: type-punning?

        j.j.fishbat@gma il.com wrote:
        Hi all
        >
        I have a program which, with inessential details removed,
        looks like this:
        >
        --
        #include <stdlib.h>
        #include <stdio.h>
        >
        static int punme(void** dat,size_t newsize)
        {
        void *newdat = realloc(*dat,ne wsize);
        >
        if (! newdat) return 1;
        >
        *dat = newdat;
        >
        return 0;
        }
        >
        int main (void)
        {
        char *dat = malloc(30);
        >
        int ret = punme((void**)& dat,40);
        >
        printf("punme returns %i\n",ret);
        >
        return 0;
        }
        --
        >
        My compiler (gcc -Wall -O3) tells me that "typepun.c: 19:
        warning: dereferencing type-punned pointer will break
        strict-aliasing rules". The code works as expected, ie,
        prints that punme returns 0.
        >
        Who is the idiot? Me or the compiler?
        ...
        You perform memory reinterpretatio n in your code. You have an lvalue
        'dat' of type 'char*', which is reinterpreted as an lvalue of type
        'void*' by using a conversion followed by a dereference '*(void**)
        &dat'. In general, accessing the lvalue obtained by such
        reinterpretatio n causes undefined behavior in C, unless the types are
        "similar enough". In strict-aliasing mode (implied by -O3) GCC assumes
        that such reinterpretatio ns are not performed in the code. This is why
        it issues the warning.

        --
        Best regards,
        Andrey Tarasevich

        Comment

        • j.j.fishbat@gmail.com

          #5
          Re: type-punning?

          Hi
          This doesn't fix the problem. This merely reorganises the code in a form
          that the compiler may happen to not warn about.
          >
          The problem is that dat is defined as char *. You can't pretend it's
          defined as a void *. The language doesn't let you.
          are you saying that

          ---
          int foo(void* bar)
          {
          char *st = (char*)bar;
          :
          }

          :
          :

          char *bar;
          :

          foo((void*)bar) ;
          ---

          is not possible in C?

          Are we using the same language?
          A fixed version looks
          like
          >
          #include <stdlib.h>
          #include <stdio.h>
          >
          static void *punme(void *dat, size_t newsize) {
          return realloc(dat, newsize);
          >
          }
          >
          int main (void)
          {
          char *dat = malloc(30);
          char *newdat = punme(dat, 40); /* or call realloc directly */
          if (newdat != NULL) dat = newdat;
          printf("punme would have returned %d\n", (newdat == NULL ? 1 : 0));
          return 0;
          >
          }
          Part of the "inessentia l detail" omitted is that
          I use the return value of punme() for other purposes,
          I specifically want to modify dat (char*) which I pass
          to punme() by reference.

          Is there really no way at all to modify a generic
          pointer by passing a reference to it to a function?
          Are C programmers condemned to return structs
          (one member of which is the void* modified) whenever
          we want to do generic programming??

          Thanks!

          Comment

          • Andrey Tarasevich

            #6
            Re: type-punning?

            j.j.fishbat@gma il.com wrote:
            are you saying that
            >
            ---
            int foo(void* bar)
            {
            char *st = (char*)bar;
            :
            }
            >
            :
            :
            >
            char *bar;
            :
            >
            foo((void*)bar) ;
            ---
            >
            is not possible in C?
            This is not even remotely the same thing as your original code. This
            version uses _conversion_. Your original version performed memory
            reinterpretatio n.

            Compare the following two examples:

            Let's assume that sizeof(float) is the same as sizeof(int)

            float f = 1.0;
            int i;

            Example 1. Conversion

            i = (int) f;
            /* Here we can expect i to be equal to 1 */
            assert(i == 1);

            Example 2. Memory reinterpretatio n

            i = *(int*) &f;
            /* Can we say anything about the value of i here? NO! */

            The difference between these tow examples is exactly the difference
            between your two code samples.
            Is there really no way at all to modify a generic
            pointer by passing a reference to it to a function?
            No.

            --
            Best regards,
            Andrey Tarasevich

            Comment

            • Harald van =?UTF-8?b?RMSzaw==?=

              #7
              Re: type-punning?

              On Tue, 06 May 2008 11:17:38 -0700, j.j.fishbat wrote:
              Is there really no way at all to modify a generic pointer by passing a
              reference to it to a function?
              No, there's really no way to do that, any more than it's possible to
              modify a generic integer (char, short, int, long) by passing a reference
              to it to a function. It's obvious that that won't work on most systems
              with integers, because different integer types are represented
              differently. When you consider that C allows different pointer types to be
              represented differently too (even though most implementations don't do
              so), I hope you can see why what you're asking for is not possible.

              Comment

              • Kenneth Brody

                #8
                Re: type-punning?

                j.j.fishbat@gma il.com wrote:
                [...]
                The problem is that dat is defined as char *. You can't pretend it's
                defined as a void *. The language doesn't let you.
                >
                are you saying that
                >
                ---
                int foo(void* bar)
                {
                [...]
                :
                char *bar;
                :
                foo((void*)bar) ;
                ---
                >
                is not possible in C?
                That's not the same thing. In your original post, you had:

                static int punme(void **dat,size_t newsize)
                ...
                char *dat = malloc(30);
                int ret = punme((void **)&dat,40);

                Here, you are telling punme that is gets passed a pointer to "void *"
                but you are passing a pointer to "char *". Note "pointer to" in both
                of those pieces.

                [...]
                Is there really no way at all to modify a generic
                pointer by passing a reference to it to a function?
                Are C programmers condemned to return structs
                (one member of which is the void* modified) whenever
                we want to do generic programming??
                But you are not modifying a "generic pointer". Or, at least, you
                are not _passing_ a "generic pointer".

                Consider the fact that a "void *" and a "char *" need not be stored
                the same way. Yes, a function can take a "void *" and you can pass
                it a "char *", but that is because the value will be converted to a
                "void *" before being passed. However, what you are trying to do is
                pass a pointer to "char *", while a pointer to "void *" is expected.
                No conversion of your original "char *" will take place. This is
                the same as if you had:

                void foo(double *pt)
                ...
                float f;
                foo((double *)&f);

                In your case, the program "works" because, in all likelihood, the
                representation of "void *" and "char *" are the same.

                --
                +-------------------------+--------------------+-----------------------+
                | Kenneth J. Brody | www.hvcomputer.com | #include |
                | kenbrody/at\spamcop.net | www.fptech.com | <std_disclaimer .h|
                +-------------------------+--------------------+-----------------------+
                Don't e-mail me at: <mailto:ThisIsA SpamTrap@gmail. com>

                Comment

                • Harald van =?UTF-8?b?RMSzaw==?=

                  #9
                  Re: type-punning?

                  On Tue, 06 May 2008 14:34:57 -0400, Kenneth Brody wrote:
                  Consider the fact that a "void *" and a "char *" need not be stored the
                  same way.
                  Actually, void * and char * are special in that they must be stored the
                  same way. It may be better to pretend they too might be stored
                  differently, as you still aren't allowed to access one as the other anyway
                  (except in special circumstances), and this guarantee does not extend to
                  other pointer types, but I'm mentioning it for completeness.

                  Comment

                  • j.j.fishbat@gmail.com

                    #10
                    Re: type-punning?


                    Hi all
                    Consider the fact that a "void *" and a "char *" need not be stored
                    the same way.
                    OK, I am the idiot then. I had thought, for the past 10 years
                    C programming, that a pointer to char was the same as a
                    pointer to int was the same as a pointer to foobar_t, the only
                    difference being what it pointed to. And that this fact enables
                    one to cast to void* to enable generic programming.

                    I see I'll have to rewrite some code.

                    Thank you again to all who replied!

                    Jim

                    Comment

                    • lawrence.jones@siemens.com

                      #11
                      Re: type-punning?

                      Andrey Tarasevich <andreytarasevi ch@hotmail.comw rote:
                      >
                      You perform memory reinterpretatio n in your code. You have an lvalue
                      'dat' of type 'char*', which is reinterpreted as an lvalue of type
                      'void*' by using a conversion followed by a dereference '*(void**)
                      &dat'. In general, accessing the lvalue obtained by such
                      reinterpretatio n causes undefined behavior in C, unless the types are
                      "similar enough".
                      In particular, (char *) and (void *) are required to have the same
                      representation and alignment requirements, so reinterpretatio n is almost
                      (but not quite) guaranteed to work in that case. The same is not true
                      of other pointer types, however, and reinterpretatio n involving other
                      pointer types *does* fail on some implementations .

                      -- Larry Jones

                      Apparently I was misinformed. -- Calvin

                      Comment

                      • Kenneth Brody

                        #12
                        Re: type-punning?

                        j.j.fishbat@gma il.com wrote:
                        >
                        Hi all
                        >
                        Consider the fact that a "void *" and a "char *" need not be stored
                        the same way.
                        >
                        OK, I am the idiot then. I had thought, for the past 10 years
                        C programming, that a pointer to char was the same as a
                        pointer to int was the same as a pointer to foobar_t, the only
                        difference being what it pointed to. And that this fact enables
                        one to cast to void* to enable generic programming.
                        >
                        I see I'll have to rewrite some code.
                        >
                        Thank you again to all who replied!
                        Well, you have probably been programming on a platform on which all
                        of the data pointer types are represented the same way. (And, I
                        must confess, so have I.) However, C does not guarantee such a
                        thing, and I believe others have posted here examples of systems
                        in which they are, in fact, represented differently.

                        Yes, you can convert from any pointer to "void *" and back again.
                        But, you can't, for example, convert from "int *" to "void *" and
                        then to "long *", and dereference that pointer without invoking
                        UB. (In fact, the converting to "long *" step itself may be UB.)

                        Note, too, that Mr. van D?k (sorry about the mangling of the name,
                        but my newsreader is obviously not UTF-8 compliant) says that the
                        standard does say that "void *" and "char *" are special cases
                        which must be represented the same way. If that is true, then
                        your original example will "work" as-is, but only because you
                        are converting "char **" to "void **". It's not guaranteed to
                        work with, for example, "int **" to "void **", in a "portable"
                        manner. Again, it will probably "work" on your systems, because
                        they are likely to be storing "void *" and "int *" in the same
                        representation.

                        --
                        +-------------------------+--------------------+-----------------------+
                        | Kenneth J. Brody | www.hvcomputer.com | #include |
                        | kenbrody/at\spamcop.net | www.fptech.com | <std_disclaimer .h|
                        +-------------------------+--------------------+-----------------------+
                        Don't e-mail me at: <mailto:ThisIsA SpamTrap@gmail. com>

                        Comment

                        • Harald van =?UTF-8?b?RMSzaw==?=

                          #13
                          Re: type-punning?

                          On Tue, 06 May 2008 16:26:19 -0400, Kenneth Brody wrote:
                          Note, too, that Mr. van D?k (sorry about the mangling of the name, but
                          my newsreader is obviously not UTF-8 compliant)
                          <OTIt could be that your system font simply doesn't contain the
                          character, but the newsreader still is UTF-8 compliant. :) </OT>
                          says that the standard
                          does say that "void *" and "char *" are special cases which must be
                          represented the same way. If that is true,
                          6.2.5p27:
                          "A pointer to void shall have the same representation and alignment
                          requirements as a pointer to a character type."
                          then your original example
                          will "work" as-is, but only because you are converting "char **" to
                          "void **".
                          No, and I had tried to make that explicit. I wrote "you still aren't
                          allowed to access one as the other anyway (except in special
                          circumstances)" . Those special circumstances would be using memcpy or an
                          union to copy between distinct pointer types. Using *(char **) &p when p
                          is defined as void *p, or vice versa, is not one of those special
                          circumstances, and when you do that, the behaviour is still undefined,
                          just like the behaviour is undefined if you use *(int **) &p on systems
                          where int * has the same representation and alignment requirements as
                          void *.

                          Comment

                          • Ben Bacarisse

                            #14
                            Re: type-punning?

                            j.j.fishbat@gma il.com writes:
                            Is there really no way at all to modify a generic
                            pointer by passing a reference to it to a function?
                            Two people have said "no" here and that is because they have, quote
                            reasonably, taken this phrase in the context of your previous example.
                            However, I think you can do what you ask, provided you stick to the
                            narrowest interpretation of the quote above. (If not, I will get shot
                            down by the experts and we will both be wiser.)

                            What you can't do is leave the safety guarantees provided by the void
                            * type. So, you /can/ do this (example only, I don't recommend it!):

                            int my_realloc(void **ptr, size_t sz)
                            {
                            void *newp = realloc(*ptr, sz);
                            if (newp == NULL) {
                            free(*ptr);
                            return 0;
                            }
                            *ptr = newp;
                            return 1;
                            }


                            later...

                            char *sp = malloc(20);
                            void *tmp;
                            ...
                            tmp = sp;
                            if (!my_realloc(&t mp, 30))
                            printf("Failed! \n");
                            else sp = tmp;
                            ...

                            and you can do the same even for the types that do not explicitly use
                            the same representation: int *, or struct pointers, etc. The
                            assignments to a void * object make it work in a portable way.

                            This may not be close enough to what you want to be of any use, but I
                            hope it clarifies some things.

                            --
                            Ben.

                            Comment

                            • Default User

                              #15
                              Re: type-punning?

                              Harald van D?k wrote:
                              On Tue, 06 May 2008 16:26:19 -0400, Kenneth Brody wrote:
                              Note, too, that Mr. van D?k (sorry about the mangling of the name,
                              but my newsreader is obviously not UTF-8 compliant)
                              >
                              <OTIt could be that your system font simply doesn't contain the
                              character, but the newsreader still is UTF-8 compliant. :) </OT>
                              It's a char set problem. Here's the header from his message:

                              Content-Type: text/plain; charset=us-ascii




                              Brian

                              Comment

                              Working...