home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #31 / NN_1992_31.iso / spool / comp / std / c / 3313 < prev    next >
Encoding:
Text File  |  1993-01-02  |  6.3 KB  |  152 lines

  1. Newsgroups: comp.std.c
  2. Path: sparky!uunet!uunet.ca!wildcan!sq!msb
  3. From: msb@sq.sq.com (Mark Brader)
  4. Subject: Re: Struct hack one last time (one last time)
  5. Message-ID: <1993Jan2.072908.21929@sq.sq.com>
  6. Summary: Where last element not char, legal only if special arrangements made
  7. Organization: SoftQuad Inc., Toronto, Canada
  8. References: <1992Dec31.153931.7495@hubcap.clemson.edu> <1993Jan1.173852.26630@taumet.com>
  9. Date: Sat, 2 Jan 93 07:29:08 GMT
  10. Lines: 140
  11.  
  12. > > [Is] the notorious "struct hack" was still conformant if the tail array
  13. > > in the struct [is] of type other than char[?] ... My reading of the
  14. > > earlier thread on this topic was that the hack was indeed legal, but
  15. > > all the previous examples used an array of char in their examples.
  16. > > Thus, is there any reason it would not be legal to declare:
  17. > >    struct vector {
  18. > >        int  size;
  19. > >        double v[1];
  20. > >    } *vec;
  21. > > and then code (for example):
  22. > >    vec = malloc(sizeof(struct vector) + (n - 1) * sizeof(double));
  23. > >    vec->size = n;
  24. > >    for ( i = 0;  i < vec->size;  i++ )  vec->v[i] = 0.0;
  25.  
  26. I will assume for brevity that n > 1 in the above code.  Also, I assume
  27. that "legal" means "can occur and be executed in a strictly conforming
  28. ANSI C program".
  29.  
  30. I previously believed that this code was indeed legal, but I have lately
  31. come to the opposite opinion.  Return to the classic struct hack:
  32.  
  33.     struct hack /* well, what else? */ {
  34.         int len;
  35.         char arr[1];
  36.     } *p;
  37.     p = malloc(sizeof(struct hack) + n-1);  /* where n > 1 */
  38.  
  39. In this, access to p->arr[1] is legal because p->arr decays to a
  40. pointer which points to some element of the object returned by
  41. malloc -- you can identify which one by using offsetof -- and
  42. the pointer p->arr+1 also points to some element of this object,
  43. thus meeting the requirement specified by 3.3.6/6.3.6.
  44.  
  45. (The people who still think it's not legal are either taking the point
  46. of view that that section should be interpreted to mean something
  47. other than what it says, or are confused.  If they want to disagree
  48. with the above statement, they are asked to change the Subject line
  49. to "legality of struct hack", to distinguish it from this thread.
  50. I'll ignore any posting that doesn't add something new to the extensive
  51. and rather repetitive previous discussions of the topic.)
  52.  
  53. In the other example using double, we still know that the pointer
  54. obtained by decay of vec->v points to a double which is somewhere
  55. within the object returned by the malloc(), but we do NOT know that it
  56. points to a double that would be an element of that object if we viewed
  57. it as an array of doubles.  And this distinction, I fear, is critical.
  58.  
  59. To make the example more specific, let's add these lines in the
  60. obvious places:
  61.  
  62.     double *dp;
  63. and
  64.     dp = (void *)vec;
  65.  
  66. Now (void *)vec is the same as the pointer returned by malloc(),
  67. so dp now contains a pointer to what is the first element of the
  68. object that the malloc() returned, if that object is viewed as an
  69. array of doubles.  Now suppose that sizeof(int) < sizeof(double).
  70.  
  71. If the implementation insists that doubles must be aligned on
  72. multiples of sizeof(double) bytes, then offsetof(struct vec, v)
  73. will be sizeof(double), and the pointer decayed from vec->v will
  74. be equal to dp+1.  Then vec->v[1] is the same as dp[2], all
  75. references are within the object returned by malloc() viewed as
  76. an array of doubles, and all is well.
  77.  
  78. But if the implementation allows *any* alignment of doubles, then
  79. vec->v will not be equal to dp or dp+1; instead, it will point to
  80. an object overlapping the objects that dp and dp+1 point to.
  81. The access to vec->v[1] is legal only if vec->v[0] and vec->v[1]
  82. are both elements of some array.  The arguments given earlier
  83. completely fail in this case, since the array to whose first
  84. argument dp points is useless for this purpose.  So we are left
  85. with only the array vec->v, which is too short to help.  Ergo,
  86. the code is not legal after all.
  87.  
  88. I believe it would become legal if the struct was designed in such
  89. a way that offsetof(struct vec,v) % sizeof(double) was known to be 0.
  90.  
  91. 3.5.2.1/6.5.2.1 says that padding may be inserted in a struct
  92. "as necessary to achieve the appropriate alignment".  I interpret
  93. this in such a way as to believe that the following redefinition
  94. of the struct would render the code legal:
  95.  
  96.     /* Divide a by b, rounding any fraction upward.
  97.        Both numbers assumed to be positive. */
  98.     #define DIVRUP(a,b)    (((a)+(b)-1) / (b))
  99.  
  100.     struct template
  101.     {
  102.         union {
  103.             int    size;
  104.             double    align[DIVRUP(sizeof(int),sizeof(double))];
  105.         } u;
  106.         double    v[1];
  107.     };
  108.  
  109. Of course, vec->size has to become vec->u.size.  Other tricks also
  110. exist that could be used to obtain the same alignment knowledge instead,
  111. but all the ones I can think of have some similar ugliness.
  112.  
  113.  
  114. > I don't see that the type of the array makes any difference.
  115.  
  116. It matters because objects are made up of bytes, which are the same as
  117. chars, and therefore if an object is viewed as an array of chars (as
  118. the object returned by malloc() may be viewed), then any pointer which
  119. points into the middle of that object points to one of its elements;
  120. but this is not true for larger types than char.
  121.  
  122. > I do believe that the hack is not strictly conforming, however, since
  123. > you would be using array indices outside the declared array bounds
  124. > (char or double makes no difference).  The results of doing so are
  125. > undefined.
  126.  
  127. The declared array is irrelevant; see above.
  128.  
  129. > I have thought of a plausible implementation where the struct hack
  130. > would fail, however.
  131. > An implementation is allowed to add padding to the end of a struct.
  132.  
  133. An implementation is allowed to add padding to the end of a struct
  134. (or union) "as necessary to achieve the appropriate alignment were
  135. the structure or union to be a member of an array"; no license is
  136. given to insert padding for other reasons.
  137.  
  138. > Suppose that the implementation adds space to the end of each struct
  139. > type where it encodes information used for run-time error checking.
  140. > The struct hack would fail in this case, since it would overwrite data
  141. > used by the run-time system.  I believe the implementation would still
  142. > be conforming, since we are in the arena of undefined behavior.
  143.  
  144. On the contrary, since the struct hack is legal (conforming), such an
  145. implementation must be illegal (non-conforming).
  146. -- 
  147. Mark Brader, SoftQuad Inc., Toronto    "Constrain your data early and often."
  148. utzoo!sq!msb, msb@sq.com             -- C. M. Sperberg-McQueen
  149.  
  150. This article is in the public domain.
  151.