Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C Programming > Initializing a malloc'ed struct whose fields are const with run-timevalues

Reply
Thread Tools

Initializing a malloc'ed struct whose fields are const with run-timevalues

 
 
Ben Bacarisse
Guest
Posts: n/a
 
      01-22-2013
Shao Miller <(E-Mail Removed)> writes:

> On 1/22/2013 16:13, Ben Bacarisse wrote:
>> Noob <root@127.0.0.1> writes:

<snip>
>>> 2) I'm not sure it is well-defined to memcpy stuff into some const
>>> fields. Does it tickle the cranky UB gods?

>>
>> No, I think it's fine. You are not modifying anything that is const.
>> malloced storage has no declared type and the memcpy probably makes the
>> effective type a byte array.

>
> To Ben: I think the effective type would be 'struct toto', as 'memcpy'
> is included in 6.5p6.


Interesting! My recollection was that memcpy would treat the
destination as a char array but would then set the effective type for
subsequent accesses, but the text says it sets the effective type "for
that access and for subsequent accesses".

However, I think the conclusion is still valid. To be more specific, I
think the effective type is a red herring -- I don't think it matters when
determining what can and can't be modified here.

The closest approach to UB that I can find is this (7.1.4 p1):

"Each of the following statements applies unless explicitly stated
otherwise in the detailed descriptions that follow: If an argument to
a function has an invalid value (such as [...] a pointer to
non-modifiable storage when the corresponding parameter is not
const-qualified) ..."

But that does not apply here -- the argument is a pointer to modifiable
storage no matter what it's (effective) type is about to become. Of
course, there may be some other reason why it's UB, but I can't see one.

--
Ben.
 
Reply With Quote
 
 
 
 
Ben Bacarisse
Guest
Posts: n/a
 
      01-22-2013
Shao Miller <(E-Mail Removed)> writes:

> On 1/22/2013 16:02, Ben Bacarisse wrote:
>> Ike Naar <(E-Mail Removed)> writes:
>>
>>> On 2013-01-22, Noob <root@127.0.0.1> wrote:


<snipping has lost the fact that struct toto has const qualified members>

>>> And how about returning by value?
>>>
>>> struct toto foo(int i, float f, void *p)
>>> {
>>> struct toto res = {i, f, p};
>>> return res;
>>> }

>>
>> This limits what you can do with it for the same reason. You can't
>> assign a strust toto because of its const members.

>
> Are you sure about that? 'res' undergoes lvalue conversion and no
> longer has qualified type.


Am I sure about what? I think you know that you can't assign to a
struct toto object, so are you saying if I am sure that returning this
struct type limits what one can do with it?

A pointer to allocated storage is more flexible (because you have
control over the lifetime) and the function as given is almost identical
to a simple expression: (struct toto){i, f, p}.

--
Ben.
 
Reply With Quote
 
 
 
 
Shao Miller
Guest
Posts: n/a
 
      01-23-2013
On 1/22/2013 17:37, Ben Bacarisse wrote:
> Shao Miller <(E-Mail Removed)> writes:
>
>> On 1/22/2013 16:02, Ben Bacarisse wrote:
>>> Ike Naar <(E-Mail Removed)> writes:
>>>
>>>> On 2013-01-22, Noob <root@127.0.0.1> wrote:

>
> <snipping has lost the fact that struct toto has const qualified members>
>
>>>> And how about returning by value?
>>>>
>>>> struct toto foo(int i, float f, void *p)
>>>> {
>>>> struct toto res = {i, f, p};
>>>> return res;
>>>> }
>>>
>>> This limits what you can do with it for the same reason. You can't
>>> assign a strust toto because of its const members.

>>
>> Are you sure about that? 'res' undergoes lvalue conversion and no
>> longer has qualified type.

>
> Am I sure about what? I think you know that you can't assign to a
> struct toto object, so are you saying if I am sure that returning this
> struct type limits what one can do with it?
>
> A pointer to allocated storage is more flexible (because you have
> control over the lifetime) and the function as given is almost identical
> to a simple expression: (struct toto){i, f, p}.
>


Sorry, Ben. I've approached this in my head in four ways:

1. (Non-preferred; must be wrong because of lvalue conversion)

'res', before lvalue conversion, has a compatible type with the return
type of the function, so "the value is converted as if by assignment to
an object having the return type of the function" does not apply, and
"the value of" 'res' "is returned to the caller as the value of the
function call expression"

2. (More preferred than #1)

'res' undergoes lvalue conversion and yields an unqualified type with
the value. This type is incompatible with the return type of the
function, so "the value is converted as if by assignment to an object
having the return type of the function", but the footnote reminds us
that "The return statement is not an assignment." So there might be one
or more differences.

Looking at 6.5.16p1, it cannot apply as it is, since "an object having
the return type of the function" isn't the same as "a modifiable lvalue
designating an object having the return type of the function." We need
to twist it a bit. There's also no direct discussion of conversion for
the RHS operand... That's "2 strikes". So what if we drop that bit and
move on to 6.5.16.1?

Alternatively to moving on, there's a constraint violation (as you've
mentioned) based on "modifiable". But why pick "modifiable" and not
"lvalue"?

Looking at 6.5.16.1p1, the return type of 'foo' and the unqualified type
of rvalue from 'res' satisfy the second bullet. Then p2 does indeed
refer back to 6.5.16p2, but only insofar as the type of the
assignment-expression. The type of the assignment-expression so happens
to be the unqualified type of 'struct toto', which is the same type as
the rvalue from 'res', so there's actually no conversion for the rvalue
from 'res'.

3. (Preferred)

6.7.3p4: "The properties associated with qualified types are meaningful
only for expressions that are lvalues.132)" But "the value of the
function call expression" is never an lvalue, so perhaps the return type
of a function is never qualified.

In that case, the lvalue conversion of 'res' yields an rvalue whose type
is already compatible with the return type, so "the value is converted
as if by assignment to an object having the return type of the function"
does not apply, and "the value of" 'res' "is returned to the caller as
the value of the function call expression".

If #3 is true, the wording could be a bit clearer.

4. (D'oh!)

Unfortunately, I don't think any of #1, #2, #3 is true, due to 6.7.3p9:
"...If the specification of a function type includes any type
qualifiers, the behavior is undefined.136)" (Since at least C90.) I
think that means that 'foo' can't be declared that way.

(This is Ike Naar's 'foo', obviously. Citations relative to N1570.)

--
- Shao Miller
--
"Thank you for the kind words; those are the kind of words I like to hear.

Cheerily," -- Richard Harter
 
Reply With Quote
 
Ben Bacarisse
Guest
Posts: n/a
 
      01-23-2013
Nick Bowler <(E-Mail Removed)> writes:

> On Tue, 22 Jan 2013 21:13:50 +0000, Ben Bacarisse wrote:
>> BTW, in C99 you don't even need a declaration:
>>
>> if (res != NULL) memcpy(res, &(struct toto){i, f, p}, sizeof *res);

>
> Danger, Will Robinson!
>
> Implementations are allowed to define a function-like macro called
> memcpy, and the above line will not work on implementations that do so.
> Macro rguments will be split on the commas in the compound literal,
> which is almost certainly not desirable.


Good point. I'd forgotten about that entirely. Fortunately the
compiler will tell you what's happened so this should be a sort-lived
problem.

> Therefore, it is neccesary to add parentheses (or to #undef memcpy...),
> either around the compound literal to avoid such splitting, or around
> memcpy to avoid any macro substitution at all. For example:
>
> if (res != NULL) memcpy(res, (&(struct toto){i, f, p}), sizeof *res);
> if (res != NULL) (memcpy)(res, &(struct toto){i, f, p}, sizeof *res);


I'd choose the first (ugly though it is) just because the macro might
well be there because it provides some benefit.

There might have been an argument for counting {} along with ()s when
collecting, well, arguments but it's too late now...

--
Ben.
 
Reply With Quote
 
Ben Bacarisse
Guest
Posts: n/a
 
      01-23-2013
William Ahern <william@wilbur.25thandClement.com> writes:

> Ben Bacarisse <(E-Mail Removed)> wrote:
>> Noob <root@127.0.0.1> writes:

> <snip>
>> > 2) I'm not sure it is well-defined to memcpy stuff into some const
>> > fields. Does it tickle the cranky UB gods?

>
>> No, I think it's fine. You are not modifying anything that is const.
>> malloced storage has no declared type and the memcpy probably makes the
>> effective type a byte array.

>
> C99 (N1256) 6.5p6:
>
> If a value is copied into an object having no declared type using
> memcpy or memmove, or is copied as an array of character type, then
> the effective type of the modified object for that access and for
> subsequent accesses that do not modify the value is the effective
> type of the object from which the value is copied, if it has one.
>
> I guess the effective type arises only after memcpy completes.


I'd say that "for that access and for subsequent accesses" includes the
memcpy itself, though my memory of that paragraph was as you describe --
some sort of byte array copy and only subsequently a new effective type.

Anyway, I suspect the effective type is a red herring. I don't see any
problems arising from this aspect of the code.

--
Ben.
 
Reply With Quote
 
Shao Miller
Guest
Posts: n/a
 
      01-23-2013
On 1/22/2013 16:25, William Ahern wrote:
> Shao Miller <(E-Mail Removed)> wrote:
>> On 1/22/2013 10:36, Noob wrote:

> <snip>
>>> which has several defects:
>>>
>>> 1) Apparently, C89 does not allow one to use elements "not computable
>>> at load time" in an initialization list. However, it is allowed both
>>> in C99 (right?) and in gnu89.
>>>

>
>> The initializers in an initializer list for an aggregate or union must
>> be constant expressions, and no, I do not believe that's changed in any
>> newer Standard.

>
> I believe that changed with C99. It only applies to objects of static
> storage duration. 6.7.8p4 + "exceptio probat regulam in casibus non
> exceptis".
>


Thank you, Mr. William Ahern.

On 1/22/2013 16:26, Ben Bacarisse wrote:
> I think it has.


Thank you, Ben.

On 1/22/2013 16:41, James Kuyper wrote:
> You think correctly - as of C99, that restriction only applies to
> initializers for objects of static storage duration. As of C2011, it
> also applies to initializers for objects with thread storage duration
> (6.7.9p4); only C90 imposed it on initializers for objects with
> automatic storage duration.


Thank you, Mr. James Kuyper.

On 1/22/2013 17:21, Ben Bacarisse wrote:
> Shao Miller <(E-Mail Removed)> writes:
>> On 1/22/2013 16:13, Ben Bacarisse wrote:
>>> Noob <root@127.0.0.1> writes:
>>>
>>>> #include <stdlib.h>
>>>> #include <string.h>
>>>> struct toto *foo(int i, float f, void *p)
>>>> {
>>>> struct toto s = { i, f, p };
>>>> struct toto *res = malloc(sizeof *res);
>>>> if (res != NULL) memcpy(res, &s, sizeof s);
>>>> return res;
>>>> }
>>>>
>>>> [...]
>>>>
>>>> 2) I'm not sure it is well-defined to memcpy stuff into some const
>>>> fields. Does it tickle the cranky UB gods?
>>>
>>> No, I think it's fine. You are not modifying anything that is const.
>>> malloced storage has no declared type and the memcpy probably makes the
>>> effective type a byte array.

>>
>> To Ben: I think the effective type would be 'struct toto', as 'memcpy'
>> is included in 6.5p6.

>
> Interesting! My recollection was that memcpy would treat the
> destination as a char array but would then set the effective type for
> subsequent accesses, but the text says it sets the effective type "for
> that access and for subsequent accesses".
>
> However, I think the conclusion is still valid. To be more specific, I
> think the effective type is a red herring -- I don't think it matters when
> determining what can and can't be modified here.
>
> The closest approach to UB that I can find is this (7.1.4 p1):
>
> "Each of the following statements applies unless explicitly stated
> otherwise in the detailed descriptions that follow: If an argument to
> a function has an invalid value (such as [...] a pointer to
> non-modifiable storage when the corresponding parameter is not
> const-qualified) ..."
>
> But that does not apply here -- the argument is a pointer to modifiable
> storage no matter what it's (effective) type is about to become. Of
> course, there may be some other reason why it's UB, but I can't see one.
>


My original note to Noob about effective type was not applicable to
Noob's case, but just a warning about using 'memcpy' into allocated
storage for C >= C99, in general. For example, just before the 'return':

if (res) {
int * ip = (void *) ((char *) res + offsetof(struct toto, i));
/* 'ip' not in bullets of 6.5p7 */
*ip;
}

(But I could be forgetting something else.)

--
- Shao Miller
--
"Thank you for the kind words; those are the kind of words I like to hear.

Cheerily," -- Richard Harter
 
Reply With Quote
 
Ben Bacarisse
Guest
Posts: n/a
 
      01-23-2013
Shao Miller <(E-Mail Removed)> writes:

> On 1/22/2013 17:37, Ben Bacarisse wrote:
>> Shao Miller <(E-Mail Removed)> writes:
>>
>>> On 1/22/2013 16:02, Ben Bacarisse wrote:
>>>> Ike Naar <(E-Mail Removed)> writes:
>>>>
>>>>> On 2013-01-22, Noob <root@127.0.0.1> wrote:

>>
>> <snipping has lost the fact that struct toto has const qualified members>
>>
>>>>> And how about returning by value?
>>>>>
>>>>> struct toto foo(int i, float f, void *p)
>>>>> {
>>>>> struct toto res = {i, f, p};
>>>>> return res;
>>>>> }
>>>>
>>>> This limits what you can do with it for the same reason. You can't
>>>> assign a strust toto because of its const members.
>>>
>>> Are you sure about that? 'res' undergoes lvalue conversion and no
>>> longer has qualified type.

>>
>> Am I sure about what? I think you know that you can't assign to a
>> struct toto object, so are you saying if I am sure that returning this
>> struct type limits what one can do with it?
>>
>> A pointer to allocated storage is more flexible (because you have
>> control over the lifetime) and the function as given is almost identical
>> to a simple expression: (struct toto){i, f, p}.
>>

>
> Sorry, Ben. I've approached this in my head in four ways:
>
> 1. (Non-preferred; must be wrong because of lvalue conversion)
>
> 'res', before lvalue conversion, has a compatible type with the return
> type of the function, so "the value is converted as if by assignment
> to an object having the return type of the function" does not apply,
> and "the value of" 'res' "is returned to the caller as the value of
> the function call expression"
>
> 2. (More preferred than #1)
>
> 'res' undergoes lvalue conversion and yields an unqualified type with
> the value. This type is incompatible with the return type of the
> function, so "the value is converted as if by assignment to an object
> having the return type of the function", but the footnote reminds us
> that "The return statement is not an assignment." So there might be
> one or more differences.
>
> Looking at 6.5.16p1, it cannot apply as it is, since "an object having
> the return type of the function" isn't the same as "a modifiable
> lvalue designating an object having the return type of the function."
> We need to twist it a bit. There's also no direct discussion of
> conversion for the RHS operand... That's "2 strikes". So what if we
> drop that bit and move on to 6.5.16.1?
>
> Alternatively to moving on, there's a constraint violation (as you've
> mentioned) based on "modifiable". But why pick "modifiable" and not
> "lvalue"?
>
> Looking at 6.5.16.1p1, the return type of 'foo' and the unqualified
> type of rvalue from 'res' satisfy the second bullet. Then p2 does
> indeed refer back to 6.5.16p2, but only insofar as the type of the
> assignment-expression. The type of the assignment-expression so
> happens to be the unqualified type of 'struct toto', which is the same
> type as the rvalue from 'res', so there's actually no conversion for
> the rvalue from 'res'.
>
> 3. (Preferred)
>
> 6.7.3p4: "The properties associated with qualified types are
> meaningful only for expressions that are lvalues.132)" But "the value
> of the function call expression" is never an lvalue, so perhaps the
> return type of a function is never qualified.
>
> In that case, the lvalue conversion of 'res' yields an rvalue whose
> type is already compatible with the return type, so "the value is
> converted as if by assignment to an object having the return type of
> the function" does not apply, and "the value of" 'res' "is returned to
> the caller as the value of the function call expression".
>
> If #3 is true, the wording could be a bit clearer.
>
> 4. (D'oh!)
>
> Unfortunately, I don't think any of #1, #2, #3 is true, due to
> 6.7.3p9: "...If the specification of a function type includes any type
> qualifiers, the behavior is undefined.136)" (Since at least C90.) I
> think that means that 'foo' can't be declared that way.


You wrote eight paragraphs of stuff that you don't think is true?

How do you interpret 6.7.3 p9 so that the function above is undefined,
but const char *int_to_str(int) is not?

> (This is Ike Naar's 'foo', obviously. Citations relative to N1570.)


--
Ben.
 
Reply With Quote
 
Shao Miller
Guest
Posts: n/a
 
      01-23-2013
On 1/22/2013 15:59, Noob wrote:
> Shao Miller wrote:
>> On 1/22/2013 12:59, Ike Naar wrote:
>>> On 2013-01-22, Noob wrote:
>>>> #include <stdlib.h>
>>>> #include <string.h>
>>>> struct toto *foo(int i, float f, void *p)
>>>> {
>>>> struct toto s = { i, f, p };
>>>> struct toto *res = malloc(sizeof *res);
>>>> if (res != NULL) memcpy(res, &s, sizeof s);
>>>
>>> Why memcpy instead of an assignment?
>>> if (res != NULL) *res = s;

>>
>> Because they amount to the same thing, behind the scenes, on a few
>> implementations? Lots of people still do:
>>
>> struct tag s;
>> memset(&s, 0, sizeof s);
>>
>> instead of:
>>
>> struct tag s = { 0 };
>>
>> But the latter seems better (when possible).
>>
>>>> return res;
>>>> }
>>>
>>> And how about returning by value?
>>>
>>> struct toto foo(int i, float f, void *p)
>>> {
>>> struct toto res = {i, f, p};
>>> return res;
>>> }
>>>

>>
>> This has the same problem with the initializer list, though.
>>
>> I would guess that 'malloc' was being used for lifetime considerations,
>> but obviously only Noob knows.

>
> As I often do, my simplification went a bit too far, so I'll just
> describe the actual use-case.
> (This involves two threads on a POSIX-compliant platform.)
>
> I have a foo_start function which malloc's space for a "context"
> struct, populates the struct according to the function's parameters,
> then spawns a new thread which is passed this context. (This is why
> dynamic allocation must be used.)
>
> To make matters more complex, I have a flexible array at the end of
> the struct.
>
> Basically, struct ctx is defined this way:
>
> struct ctx {
> int file_idx;
> const char *buf;
> sem_t sem;
> char path[];
> };
>
> void *foo_run(void *arg) {
> struct ctx *ctx = arg;
> do stuff in an infinite loop, according to ctx
> }
>
> void foo_start(int param1, int param2, ...)
> {
> struct ctx *ctx = malloc(sizeof *ctx + paramx);
> populate the fields of ctx;
> spawn(foo_run, ctx);
> }
>
> In my current version, I don't have any const qualifiers in my code.
> I like to look at the assembly code generated by the compiler, and I
> noticed that every time I need some field from ctx, the compiler has
> to reload it, instead of caching the value in a register.
>
> As far as I understand, this is expected: the address of the struct
> could be stored anywhere, and any function in a different translation
> unit could "pull the rug" from under me. Except that *I* wrote the
> code, and I *know* (by design) that most fields in the struct do NOT
> change after init.
>
> So I set out to sprinkle a few "const" qualifiers here and there, to
> see if that would convince the compiler that some optimizations are
> indeed possible (I know this looks like a clear case of premature
> optimization, but I figured I might as well learn something new along
> the way!)
>
> I don't think I can just const-qualify the entire struct, because
> 1) the prototype for a thread's entry point is imposed by POSIX
> 2) const-qualifying a pointer parameter is only a contract between
> the user and the function's implementer, saying "my function won't
> touch your preciousss struct", it doesn't say that the struct won't
> be changed by something else.
>
> Whereas, I was under the impression that a const-qualified field
> means "hear, hear, this field SHALL NEVER change!"
>
> Anyway, I'd be happy to hear the word from the regs, and/or take
> this to comp.unix.programmer at some point (although I do believe
> that the core of my question is a C question, not POSIX).
>


Although I gave the wrong answer about C >= C99 aggregate/union
initializer-list, if you'll still permit, I'd like to offer some
thoughts on why using 'offsetof' (as a previous post did) might have
benefit:

1. Your non-constant initializers require C >= C99.

2. The work of initialization and then copying to allocated storage
is more work (but prettier, perhaps) than assigning directly to the
allocated storage, unless working with automatic objects has a special
advantage in terms of speed.

3. For C >= C99, the effective types for the sub-objects in the
allocated storage will remain modifiable, but the 'struct toto *' used
to access any of those will only allow 'const' access; perhaps the
artificial limitation you desire? "Internal" functions could use
'offsetof', if needed.

Using 'memcpy' from another 'struct toto' establishes the effective type
of the allocated storage and then it can be argued that there are
complications from pointing into the storage with a pointer to an
unqualified referenced type, then using such pointers to read the value.
(Although I think that argument would disappear if you use a 'const
int *' to read the 'const int' sub-object of the allocated storage,
instead.)

--
- Shao Miller
--
"Thank you for the kind words; those are the kind of words I like to hear.

Cheerily," -- Richard Harter
 
Reply With Quote
 
Tim Rentsch
Guest
Posts: n/a
 
      01-23-2013
Noob <root@127.0.0.1> writes:

> Is it possible, in standard C89 and C99, to initialize a
> struct whose fields are const with values only known at
> run-time?


Yes, although it's easier in C99, because of the point you
identify below (point 1).

> [snip] The best I could come up with is:
>
> #include <stdlib.h>
> #include <string.h>
> struct toto *foo(int i, float f, void *p)
> {
> struct toto s = { i, f, p };
> struct toto *res = malloc(sizeof *res);
> if (res != NULL) memcpy(res, &s, sizeof s);
> return res;
> }
>
> which has several defects:
>
> 1) Apparently, C89 does not allow one to use elements "not
> computable at load time" in an initialization list. However, it
> is allowed both in C99 (right?) and in gnu89.


Right. I recommend -std=c99 in preference to -std=gnu89 (and
of course -pedantic-errors). According to another posting you
want to use flexible array members so apparently you will be
using C99 anyway.

> 2) I'm not sure it is well-defined to memcpy stuff into some
> const fields. [Is the behavior well-defined?]


Yes, well-defined behavior, since the space being copied into
is allocated memory. Moreover copying from a declared struct
of the same type does the right thing in terms of effective
type rules.
 
Reply With Quote
 
Tim Rentsch
Guest
Posts: n/a
 
      01-23-2013
Ian Collins <(E-Mail Removed)> writes:

> Ike Naar wrote:
>>> [snip]

>>
>> And how about returning by value?
>>
>> struct toto foo(int i, float f, void *p)
>> {
>> struct toto res = {i, f, p};
>> return res;
>> }

>
> Is that legal? struct toto has const members, so can it be
> returned by value from a function?


Can you name a constraint or a 'shall' provision that it
violates? I can't find one. If there is no such violation,
then the behavior is well-defined (and I believe that is in
fact the case).

Furthmore this result is more useful than it might appear at
first blush, because such functions can be used as part of
initialization:

struct toto somethin = foo( 0, 3.14, malloc( 97 ) );

Of course there isn't much point for this particular
definition of foo(), but it's easy to imagine other
"constructor" functions that would be quite handy to have
around.
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Can *common* struct-members of 2 different struct-types, that are thesame for the first common members, be accessed via pointer cast to either struct-type? John Reye C Programming 28 05-08-2012 12:24 AM
print struct fields and its member structs' fields recursively, generically call_me_anything C++ 4 09-30-2007 10:12 PM
const vector<A> vs vector<const A> vs const vector<const A> Javier C++ 2 09-04-2007 08:46 PM
Casting int'** to 'const int * const * const' dosn't work, why? Jonas.Holmsten@gmail.com C Programming 11 07-01-2007 06:16 PM
struct my_struct *p = (struct my_struct *)malloc(sizeof(struct my_struct)); Chris Fogelklou C Programming 36 04-20-2004 08:27 AM



Advertisments