Object persistence in C

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • jacob navia

    #1

    Object persistence in C

    I am writing software to make a general storage
    facility of any kind of objects to/from disk.

    The intermeidate format used is XML, using the schema
    (modified a bit) of Microsoft: xmlns="x-schema:xop-schema.xml"

    Operation:
    ----------
    The software generates several C functions that implement the
    writing of the XML. To make things more concrete suppose
    the following setup:

    typedef struct tagG {
    int tab[10];
    } Tab;
    typedef struct tagstruct {
    char a;
    short b;
    int c;
    unsigned d;
    long e;
    long long f;
    long double g;
    double h;
    char * str;
    Tab tab;
    struct tagstruct *Next;
    } structure;

    The "wizard" software generates the following functions:
    ----------------------------------------------
    //@ Serialization function for structure structure
    int structureSerial ize(structure *data,FILE *out)
    {
    int i;
    unsigned char *p;
    if (data == NULL)
    return 0;
    if (!initialized) {
    InitXmlWriter(o ut);
    initialized=1;
    }
    fprintf(out,"<O bject id=\"ID%x\"
    typename=\"stru cture\">\n",(in t)data);
    fprintf(out,"\t <byte name=\"a\">%d</byte>\n",data->a);
    fprintf(out,"\t <int name=\"b\">%d</int>\n",data->b);
    fprintf(out,"\t <int name=\"c\">%d</int>\n",data->c);
    fprintf(out,"\t <unsignedInt
    name=\"d\">%u</unsignedInt>\n" ,data->d);
    fprintf(out,"\t <int name=\"e\">%d</int>\n",data->e);
    fprintf(out,"\t <long name=\"f\">%ll</long>\n",data->f);
    // Type long double not supported natively.
    // Using hexadecimal encoding
    p = (unsigned char *)&data->g;
    fprintf(out,"\t <bin.hex name=\"g\">");
    for(i=0; i<12;i++) {
    fprintf(out,"%x ",*(p++) & 0xff);
    }
    fprintf(out,"</bin.hex>\n");
    fprintf(out,"\t <double name=\"h\">%.15 g</double>\n",data->h);
    // Assume char * points to strings
    fprintf(out,
    "\t<string name=\"str\" xml:space=\"pre serve\">%s</string>\n",
    data->str);
    fprintf(out,"\t <IDREF name=\"tab\">ID %x</IDREF>\n",&data->tab);
    fprintf(out,"\t <IDREF name=\"Next\">I D%x</IDREF>\n",data->Next);
    fprintf(out,"</Object>\n");
    structureSerial ize(data->Next,out); // follow the Next pointer
    TabSerialize(&d ata->tab,out); // Follow embedded structures
    return 1;
    }
    -----------------------------------------------------------------
    This function, when called will generate the following xml:
    ----------------------------------------------------
    <Object id="ID12ff00" typename="struc ture">
    <byte name="a">-56</byte>
    <int name="b">3876</int>
    <int name="c">-254</int>
    <unsignedInt name="d">598877 </unsignedInt>
    <int name="e">777899 </int>
    <bin.hex name="g">000000 080ff7f00</bin.hex>
    <double name="h">687.98 8877</double>
    <string name="str" xml:space="pres erve">A string</string>
    <IDREF name="tab">ID12 ff40</IDREF>
    <IDREF name="Next">ID0 </IDREF>
    </Object>
    ---------------------------------------------------------

    Design principles:
    ------------------

    1) The software will follow pointers and should be able to cope with
    complicated and messy graphs, even if they contain loops.
    To do this it records the address of each object stored.
    (Not shown in the example above)
    2) Since the address of each object is unique, the implementation
    contains no embedded objects, just references (pointers) to
    other objects. All objects are stored under the ObjectStore
    tag (not shown).

    3) Open issues are what to do with:
    A) Unions. In my opinion there is no way to know which of the
    members of the union is valid, so unions will not be followed
    and just stored in binary form.
    B) Function pointers. There is no easy way to know what is
    the name of the function stored in a function pointer.
    Storing the pointer may be useful if the program is loaded
    at the same address.

    I have followed a bit the literature about this, and I have never
    seen any C implementation. Just C++ ones, where the problems are
    much bigger than in C since they have to cope with multiple
    heritance hierarchies, templates, whatever. Happily in C everything
    is much simpler.

    Questions:

    Are any of you aware of an implementation of this in C?

    What would you propose for unions and function pointers?

    Are there any other standards for datatypes in XML besides
    the one mentioned above?

    Thanks in advance for your time

    jacob
  • Jonathan Bartlett

    #2
    Re: Object persistence in C

    jacob navia wrote:[color=blue]
    > I am writing software to make a general storage
    > facility of any kind of objects to/from disk.[/color]

    You might try comp.programmin g, since they deal in a lot of the
    algorithmic questions.

    Jon
    ----
    Learn to program using Linux assembly language

    Comment

    • Eric Sosman

      #3
      Re: Object persistence in C



      jacob navia wrote:[color=blue]
      > I am writing software to make a general storage
      > facility of any kind of objects to/from disk.
      > [...]
      > The "wizard" software generates the following functions:
      > ----------------------------------------------
      > //@ Serialization function for structure structure
      > int structureSerial ize(structure *data,FILE *out)
      > {
      > int i;
      > unsigned char *p;
      > if (data == NULL)
      > return 0;
      > if (!initialized) {
      > InitXmlWriter(o ut);
      > initialized=1;
      > }[/color]

      Is `initialized' a static variable somewhere? If so,
      it seems you can have only one XmlWriter stream active at
      a time, or maybe even at all.

      A possible alternative would be to wrap the FILE* in
      a struct of its own along with whatever state variables
      are needed, so you can do

      XmlWriter *outxml = NewXmlWriter(ou t);

      .... and then pass an XmlWriter* to all the wizard-generated
      ("charmed?") functions.
      [color=blue]
      > fprintf(out,"<O bject id=\"ID%x\"
      > typename=\"stru cture\">\n",(in t)data);[/color]

      Non-portable (as I expect you know), since the conversion
      from pointer to int is implementation-defined and perhaps
      meaningless. Even if the conversion does something simple
      like "just copy the bits," the generated object IDs might
      not be unique (if int is narrower than pointer, say, or if
      dynamic memory management re-uses a free()d object's memory).
      [color=blue]
      > fprintf(out,"\t <byte name=\"a\">%d</byte>\n",data->a);
      > fprintf(out,"\t <int name=\"b\">%d</int>\n",data->b);
      > fprintf(out,"\t <int name=\"c\">%d</int>\n",data->c);
      > ...[/color]

      Bleah. Have you considered a table-driven solution?
      [color=blue]
      > // Type long double not supported natively.
      > // Using hexadecimal encoding
      > p = (unsigned char *)&data->g;
      > fprintf(out,"\t <bin.hex name=\"g\">");
      > for(i=0; i<12;i++) {
      > fprintf(out,"%x ",*(p++) & 0xff);
      > }[/color]

      Non-portable, of course.
      [color=blue]
      > structureSerial ize(data->Next,out); // follow the Next pointer
      > TabSerialize(&d ata->tab,out); // Follow embedded structures[/color]

      I'd have expected these to be done in the opposite order
      (but I haven't read the M'soft specs). Either way, though,
      using recursion to chase what might be a long linked list is
      not a wonderful idea.
      [color=blue]
      > return 1;[/color]

      If `1' means "success," maybe this should be written
      as `return !ferror(out);' or some such.
      [color=blue]
      > 3) Open issues are what to do with:
      > A) Unions. In my opinion there is no way to know which of the
      > members of the union is valid, so unions will not be followed
      > and just stored in binary form.[/color]

      Hence non-portable.
      [color=blue]
      > B) Function pointers. There is no easy way to know what is
      > the name of the function stored in a function pointer.
      > Storing the pointer may be useful if the program is loaded
      > at the same address.[/color]

      ... and hasn't been recompiled or even relinked, and
      hasn't been loaded with a newer version of a shared library,
      and isn't running under a debugger and ...

      There's also the problem that C doesn't define the
      conversion of a function pointer to any numeric datum; the
      only way to get a portable representation would be to deal
      with the pointer's constituent bytes. The byte stream would
      be interpretable by but meaningless to a recipient other than
      the same program (if lucky), hence non-portable.

      If you have a table of "pointable" functions you can
      translate the pointer to a name easily enough -- and such
      a table would seem necessary on the receiving end, to get
      from name back to function pointer again. If you get hold
      of a function pointer whose target is not in your table,
      I think you should announce a serialization failure.
      [color=blue]
      > Questions:
      >
      > Are any of you aware of an implementation of this in C?
      >
      > What would you propose for unions and function pointers?[/color]

      If you can't support them usefully, don't support them
      at all. Opinion only; YMMV.
      [color=blue]
      > Are there any other standards for datatypes in XML besides
      > the one mentioned above?[/color]

      I don't know. Probably. My counter-question: Since you're
      committed to a non-portable representation anyhow (c.f. the
      treatment of `long double'), why fool around with XML? What
      advantage does it offer if the portably-packaged content isn't
      itself portable?

      --
      Eric.Sosman@sun .com

      Comment

      • Bilgehan.Balban@gmail.com

        #4
        Re: Object persistence in C

        jacob navia wrote:[color=blue]
        > 3) Open issues are what to do with:
        > A) Unions. In my opinion there is no way to know which of the
        > members of the union is valid, so unions will not be followed
        > and just stored in binary form.
        > B) Function pointers. There is no easy way to know what is
        > the name of the function stored in a function pointer.
        > Storing the pointer may be useful if the program is loaded
        > at the same address.
        > What would you propose for unions and function pointers?
        >
        > jacob[/color]

        You could provide serialisation functions that take a list of union
        types and/or already assigned function pointers for that particular
        structure, in order of appeareance in the structure. You could easily
        do this by overloading the function, using your C compiler with
        overloading extensions ;->

        This is the way I would have done it. Probably you have already thought
        of better solutions.

        Bahadir

        Comment

        • jacob navia

          #5
          Re: Object persistence in C

          Thanks for your answer. I reply below:

          Eric Sosman wrote:[color=blue]
          >
          > jacob navia wrote:
          >[color=green]
          >>I am writing software to make a general storage
          >>facility of any kind of objects to/from disk.
          >>[...]
          >>The "wizard" software generates the following functions:
          >>----------------------------------------------
          >>//@ Serialization function for structure structure
          >>int structureSerial ize(structure *data,FILE *out)
          >>{
          >> int i;
          >> unsigned char *p;
          >> if (data == NULL)
          >> return 0;
          >> if (!initialized) {
          >> InitXmlWriter(o ut);
          >> initialized=1;
          >> }[/color]
          >
          >
          > Is `initialized' a static variable somewhere? If so,
          > it seems you can have only one XmlWriter stream active at
          > a time, or maybe even at all.
          >[/color]

          In this first implementation yes. I will improve that later, creating
          an output stream type, that will contain the static
          data.
          [color=blue]
          > A possible alternative would be to wrap the FILE* in
          > a struct of its own along with whatever state variables
          > are needed, so you can do
          >
          > XmlWriter *outxml = NewXmlWriter(ou t);
          >[/color]

          Exactly. Thanks for pointing this.
          [color=blue]
          > ... and then pass an XmlWriter* to all the wizard-generated
          > ("charmed?") functions.
          >
          >[color=green]
          >> fprintf(out,"<O bject id=\"ID%x\"
          >>typename=\"st ructure\">\n",( int)data);[/color]
          >
          >
          > Non-portable (as I expect you know), since the conversion
          > from pointer to int is implementation-defined and perhaps
          > meaningless. Even if the conversion does something simple
          > like "just copy the bits," the generated object IDs might
          > not be unique (if int is narrower than pointer, say, or if
          > dynamic memory management re-uses a free()d object's memory).
          >[/color]

          You are right. Will change that to (intptr_t) and include
          <stdint.h>

          [color=blue]
          >[color=green]
          >> fprintf(out,"\t <byte name=\"a\">%d</byte>\n",data->a);
          >> fprintf(out,"\t <int name=\"b\">%d</int>\n",data->b);
          >> fprintf(out,"\t <int name=\"c\">%d</int>\n",data->c);
          >> ...[/color]
          >
          >
          > Bleah. Have you considered a table-driven solution?[/color]

          Note that you are seeing the code generated by the "wizard", not
          the code of the wizard itself. This is straightforward to generate
          and easy to follow.
          [color=blue]
          >
          >[color=green]
          >> // Type long double not supported natively.
          >> // Using hexadecimal encoding
          >> p = (unsigned char *)&data->g;
          >> fprintf(out,"\t <bin.hex name=\"g\">");
          >> for(i=0; i<12;i++) {
          >> fprintf(out,"%x ",*(p++) & 0xff);
          >> }[/color]
          >
          >
          > Non-portable, of course.[/color]

          True. I have to investigate writing ratios of big precision
          integers, since integers are supported with 64 bit precision,
          maybe I can express a long double as a/b where a and b are 64 bit
          quantities.
          [color=blue]
          >
          >[color=green]
          >> structureSerial ize(data->Next,out); // follow the Next pointer
          >> TabSerialize(&d ata->tab,out); // Follow embedded structures[/color]
          >
          >
          > I'd have expected these to be done in the opposite order
          > (but I haven't read the M'soft specs). Either way, though,
          > using recursion to chase what might be a long linked list is
          > not a wonderful idea.
          >[/color]

          You have a point here. But I do not see an easy way out other than
          recurse.[color=blue]
          >[color=green]
          >> return 1;[/color]
          >
          >
          > If `1' means "success," maybe this should be written
          > as `return !ferror(out);' or some such.
          >[/color]

          Yes, good suggestion.
          [color=blue]
          >[color=green]
          >>3) Open issues are what to do with:
          >> A) Unions. In my opinion there is no way to know which of the
          >> members of the union is valid, so unions will not be followed
          >> and just stored in binary form.[/color]
          >
          >
          > Hence non-portable.[/color]

          I do not see what I could do other than that.
          [color=blue]
          >
          >[color=green]
          >> B) Function pointers. There is no easy way to know what is
          >> the name of the function stored in a function pointer.
          >> Storing the pointer may be useful if the program is loaded
          >> at the same address.[/color]
          >
          >
          > ... and hasn't been recompiled or even relinked, and
          > hasn't been loaded with a newer version of a shared library,
          > and isn't running under a debugger and ...
          >
          > There's also the problem that C doesn't define the
          > conversion of a function pointer to any numeric datum; the
          > only way to get a portable representation would be to deal
          > with the pointer's constituent bytes. The byte stream would
          > be interpretable by but meaningless to a recipient other than
          > the same program (if lucky), hence non-portable.
          >[/color]

          I say "may" be useful. Probably I should bail out with an error, the
          same as when I find a union.
          [color=blue]
          > If you have a table of "pointable" functions you can
          > translate the pointer to a name easily enough -- and such
          > a table would seem necessary on the receiving end, to get
          > from name back to function pointer again. If you get hold
          > of a function pointer whose target is not in your table,
          > I think you should announce a serialization failure.
          >
          >[color=green]
          >>Questions:
          >>
          >>Are any of you aware of an implementation of this in C?
          >>
          >>What would you propose for unions and function pointers?[/color]
          >
          >
          > If you can't support them usefully, don't support them
          > at all. Opinion only; YMMV.
          >[/color]

          I think I will do that. Better warn the user of unsupported
          features.
          [color=blue]
          >[color=green]
          >>Are there any other standards for datatypes in XML besides
          >>the one mentioned above?[/color]
          >
          >
          > I don't know. Probably. My counter-question: Since you're
          > committed to a non-portable representation anyhow (c.f. the
          > treatment of `long double'), why fool around with XML? What
          > advantage does it offer if the portably-packaged content isn't
          > itself portable?
          >[/color]

          Well, besides the long double problem, other types are 100% portable.
          Other ways to encode the long double in a portable way would be
          to split it in mantissa, sign and exponent, and store them in portable
          types: mantissa in a 64 bit unsigned integer (supported natively),
          sign and exponent (without the bias) as normal integers.

          Thanks for your input.

          jacob

          Comment

          • Eric Sosman

            #6
            Re: Object persistence in C



            jacob navia wrote:[color=blue]
            > Thanks for your answer. I reply below:
            >
            > Well, besides the long double problem, other types are 100% portable.[/color]

            Well, "100% portable to the implementations where they're
            portable." ;-) An `int', for example, is only portable if
            its value is in the range -32767 <= i <= 32767, a `char'
            (considered as a number) is only portable if 0 <= c <= 127,
            and other types have similar "value bands" of portability.

            And then there's floating-point: You're converting to
            text with "%.15g", but you really don't know how many decimal
            digits you need to guarantee that the receiver can read back
            exactly the same value the sender serialized. If you can use
            C99 features, consider flavors of "%a" instead.

            There's also the nasty issue of infinities and NaNs, which
            (1) are not supported on all implementations and (2) can have
            implementation-defined text formats (see 7.19.6.1/8).
            [color=blue]
            > Other ways to encode the long double in a portable way would be
            > to split it in mantissa, sign and exponent, and store them in portable
            > types: mantissa in a 64 bit unsigned integer (supported natively),
            > sign and exponent (without the bias) as normal integers.[/color]

            I still don't understand why `long double' should be any
            more troublesome than `double' or `float'. It's supported on
            all conforming C implementations (albeit with different ranges
            and precisions, but that doesn't seem to bother you for any
            of the other types). Why is `long double' special?

            --
            Eric.Sosman@sun .com

            Comment

            • jacob navia

              #7
              Re: Object persistence in C

              Eric Sosman wrote:[color=blue]
              > I still don't understand why `long double' should be any
              > more troublesome than `double' or `float'. It's supported on
              > all conforming C implementations (albeit with different ranges
              > and precisions, but that doesn't seem to bother you for any
              > of the other types). Why is `long double' special?
              >[/color]
              Because the XML reader should support natively double/float/64 bit
              ints and 32 bit ints. Long double isn't in that list.

              This is from the specs I have read at the microsoft site that
              described the xop-schema that extends the XML datatype schema.

              Comment

              • jacob navia

                #8
                Re: Object persistence in C

                Eric Sosman wrote:[color=blue]
                >[/color]
                [snip][color=blue]
                > And then there's floating-point: You're converting to
                > text with "%.15g", but you really don't know how many decimal
                > digits you need to guarantee that the receiver can read back
                > exactly the same value the sender serialized. If you can use
                > C99 features, consider flavors of "%a" instead.
                >[/color]

                Using the IEEE 754 representation DBL_DIG is 15. That's why I used
                that. Isn't that correct? What value would you use?

                And of course, if the reading machine has 16 bits ints, some values
                can't be read back as such, or if it doesn't support
                floating point, etc etc.


                Comment

                • Keith Thompson

                  #9
                  Re: Object persistence in C

                  jacob navia <jacob@jacob.re mcomp.fr> writes:[color=blue]
                  > Eric Sosman wrote:
                  > [snip][color=green]
                  >> And then there's floating-point: You're converting to
                  >> text with "%.15g", but you really don't know how many decimal
                  >> digits you need to guarantee that the receiver can read back
                  >> exactly the same value the sender serialized. If you can use
                  >> C99 features, consider flavors of "%a" instead.
                  >>[/color]
                  >
                  > Using the IEEE 754 representation DBL_DIG is 15. That's why I used
                  > that. Isn't that correct? What value would you use?[/color]

                  I'm jumping into the middle of this without having read some of the
                  previous discussion, but ...

                  If you're assuming IEEE 754 representation, you're not writing
                  completely portable C code. That's not necessarily a horrible
                  thing, but you should at least document your assumptions.

                  As for what value you should use, why not just use DBL_DIG? (Or do
                  you need DBL_DIG+1 to guarantee you can retrieve the original value?
                  Perhaps a floating-point expert can clarify.)

                  --
                  Keith Thompson (The_Other_Keit h) kst-u@mib.org <http://www.ghoti.net/~kst>
                  San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
                  We must do something. This is something. Therefore, we must do this.

                  Comment

                  • Antoine Leca

                    #10
                    Re: Object persistence in C

                    [ Jumping in the middle of a discussion which is happenning in two forums is
                    probably a bad idea, but... ]

                    En news:42c31281$0 $12661$8fcfb975 @news.wanadoo.f r,
                    jacob navia va escriure:[color=blue]
                    > Because the XML reader should support natively double/float/64 bit
                    > ints and 32 bit ints. Long double isn't in that list.[/color]

                    You are writing for Win32/64, ain't you? Then long double has the same
                    representation as double on these platforms, so the XML reader will not make
                    any problem.

                    Of course Virginia, it is not portable to make such an assumption. But it is
                    exactly as not portable as would it be to assume that long long int is _not_
                    an 128-bit wide type (which is not handled either).


                    Antoine

                    Comment

                    • jacob navia

                      #11
                      Re: Object persistence in C

                      Antoine Leca wrote:[color=blue]
                      > [ Jumping in the middle of a discussion which is happenning in two forums is
                      > probably a bad idea, but... ]
                      >
                      > En news:42c31281$0 $12661$8fcfb975 @news.wanadoo.f r,
                      > jacob navia va escriure:
                      >[color=green]
                      >>Because the XML reader should support natively double/float/64 bit
                      >>ints and 32 bit ints. Long double isn't in that list.[/color]
                      >
                      >
                      > You are writing for Win32/64, ain't you? Then long double has the same
                      > representation as double on these platforms, so the XML reader will not make
                      > any problem.[/color]

                      This is only true if you use Microsoft's compilers. Using lcc-win32,
                      or gcc will give you TRUE long doubles with 80 bits precision as
                      the machine allows.

                      Microsoft used to support true long doubles up to MSC 5.1 if
                      I remember correctly. Then, they dropped it for mysterious
                      reasons. The machine supports long doubles natively.

                      jacob

                      Comment

                      • Keith Thompson

                        #12
                        Re: Object persistence in C

                        jacob navia <jacob@jacob.re mcomp.fr> writes:[color=blue]
                        > Antoine Leca wrote:[color=green]
                        >> [ Jumping in the middle of a discussion which is happenning in two forums is
                        >> probably a bad idea, but... ]
                        >> En news:42c31281$0 $12661$8fcfb975 @news.wanadoo.f r,
                        >> jacob navia va escriure:
                        >>[color=darkred]
                        >>>Because the XML reader should support natively double/float/64 bit
                        >>>ints and 32 bit ints. Long double isn't in that list.[/color]
                        >> You are writing for Win32/64, ain't you? Then long double has the
                        >> same
                        >> representation as double on these platforms, so the XML reader will not make
                        >> any problem.[/color]
                        >
                        > This is only true if you use Microsoft's compilers. Using lcc-win32,
                        > or gcc will give you TRUE long doubles with 80 bits precision as
                        > the machine allows.[/color]

                        As far as the language is concerned, a long double type that's larger
                        than double is no more or less "true" than one that's the same size as
                        double.

                        It's common for two or more of the predefined integer types to be the
                        same size. It's probably not as common for the predefined
                        floating-point types, but it's equally valid.

                        --
                        Keith Thompson (The_Other_Keit h) kst-u@mib.org <http://www.ghoti.net/~kst>
                        San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
                        We must do something. This is something. Therefore, we must do this.

                        Comment

                        Working...