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

 
 
Ian Collins
Guest
Posts: n/a
 
      01-24-2013
Ben Bacarisse wrote:
> Ian Collins <(E-Mail Removed)> writes:
>
>> I see Sun c99, which rejects the return in foo, also accepts bar.

>
> Interesting. Is it also a C++ compiler when given other options? C++
> uses initialisation semantics for parameter passing, so it might be an
> accident. Of course it could just be that the gcc and Sun c99 authors
> are just better as reading standards!


No, I'm sure the compilers have a different front end, their error
messages have a different style. For example with

struct toto t;
t = foo( 1,2,NULL);

c99 reports

"x.c", line 17: left operand must be modifiable lvalue: op "="

while CC reports

"x.c", line 16: Error: t must be initialized.
"x.c", line 17: Error: toto has a const member i and cannot be assigned.

--
Ian Collins
 
Reply With Quote
 
 
 
 
Ian Collins
Guest
Posts: n/a
 
      01-24-2013
Shao Miller wrote:
> On 1/23/2013 17:29, Ian Collins wrote:
>> Shao Miller wrote:
>>> On 1/23/2013 08:54, Ben Bacarisse wrote:
>>>>
>>>> but 6.5.2.2 p2 says:
>>>>
>>>> "Each argument shall have a type such that its value may be assigned
>>>> to an object with the unqualified version of the type of its
>>>> corresponding parameter."
>>>>
>>>> The phrase "unqualified version of the type" can't surely be intended to
>>>> mean the recursive removal of all type qualifiers. Elsewhere it simply
>>>> means that the "top level" qualifiers are removed. If that's the right
>>>> interpretation, the call to bar should be a constraint violation.
>>>>
>>>
>>> Why? The result of the call to 'foo' yields a 'struct toto', which is
>>> not qualified (and also isn't an lvalue).

>>
>> I think Ben's point is the *parameter* value may be *assigned*, not the
>> result of the expression.
>>
>> I see Sun c99, which rejects the return in foo, also accepts bar.
>>

>
> As in, a constraint violation against 6.5.16p2?


Yes.

> If so, why would that constraint apply?


Because of 6.5.2.2 p4, if you take the word "assigned" literally.

--
Ian Collins
 
Reply With Quote
 
 
 
 
Ben Bacarisse
Guest
Posts: n/a
 
      01-24-2013
Ian Collins <(E-Mail Removed)> writes:

> Ben Bacarisse wrote:
>> Ian Collins <(E-Mail Removed)> writes:
>>
>>> I see Sun c99, which rejects the return in foo, also accepts bar.

>>
>> Interesting. Is it also a C++ compiler when given other options? C++
>> uses initialisation semantics for parameter passing, so it might be an
>> accident. Of course it could just be that the gcc and Sun c99 authors
>> are just better as reading standards!

>
> No, I'm sure the compilers have a different front end, their error
> messages have a different style.


Ah, right. Thanks. Just another data point: Comeau's online C99
compiler accepts it (both the return and the call of 'bar').

<snip>
--
Ben.
 
Reply With Quote
 
Tim Rentsch
Guest
Posts: n/a
 
      01-24-2013
Noob <root@127.0.0.1> writes:

> Ben Bacarisse wrote:
>
>> Noob wrote:
>>
>>> BartC wrote:
>>>
>>>> Noob wrote:
>>>>
>>>>> You're right, I meant:
>>>>>
>>>>> struct toto { const int i; const float f; void *const p; };
>>>>>
>>>>> And it turns out that all this work was fruitless, as gcc
>>>>> does not take advantage of the const-ness of the fields,
>>>>> and keeps reloading the values from memory every time they
>>>>> are needed...
>>>>
>>>> Where should it load them from instead?
>>>
>>> I expected the compiler to write the value to a register before
>>> the loop, and read it from there henceforth.
>>>
>>>> Perhaps there was no opportunity to keep the values resident in registers.
>>>
>>> If I convince the compiler that "i" will not change during the
>>> lifetime of the function by defining a copy on the stack:
>>>
>>> const int i = ctx->i;
>>> while ( 1 ) { use i, and the rest of the ctx }
>>>
>>> then i is indeed kept in a register (of which I have 32 on this
>>> platform). I was hoping to sprinkle const-qualifiers in the struct
>>> definition so that I wouldn't have to make copies of every const
>>> field in the ctx struct.

>>
>> You may find that using restrict (if appropriate) gives better
>> results than const. For example, given
>>
>> struct toto { int i; double d; };
>>
>> double f(struct toto *ctx, double *p)
>> {
>> double s = 0;
>> for (int i = 0; i < ctx->i; i++) {
>> s += ctx->d;
>> *p += 1;
>> }
>> return s;
>> }
>>
>> gcc won't keep ctx->d in a register because *p might alias it.
>> The code loads ctx->d every time even if cts points to a const
>> struct with const members. But making both ctx and p restrict
>> qualified does what you want.

>
> I could be wrong, but I don't think this is an aliasing
> problem.


This comment seems to me peculiar. Do you mean you think there
isn't any aliasing going on, or that you think the compiler is
smart enough to realize there isn't any aliasing going on? There
is a big difference between those two circumstances.

> The prototype for the thread's entry point is
> void *start_routine(void *arg)
> (In my case, arg is, in fact, a (struct toto *))
>
> To make your example more like mine, your function "f" should
> only take a single (struct toto *) parameter, in which case I'm
> not sure restrict-qualifying the parameter brings much? (And
> is it compatible with the expected prototype?)


If you have a function declared, for example, as

void * blah( struct whatever * );

that is compatible with defining the function thusly

void *
blah( struct whatever * restrict it ){ ... }

because of how top-level qualifiers are treated for function
parameters.

> Does it make sense to restrict-qualify struct fields?


Unlikely. Probably not impossible, but almost certainly
not a path that will help solve the problem you are
interested in solving here.
 
Reply With Quote
 
Shao Miller
Guest
Posts: n/a
 
      01-24-2013
On 1/23/2013 20:03, Ian Collins wrote:
> Shao Miller wrote:
>> On 1/23/2013 17:29, Ian Collins wrote:
>>> Shao Miller wrote:
>>>> On 1/23/2013 08:54, Ben Bacarisse wrote:
>>>>>
>>>>> but 6.5.2.2 p2 says:
>>>>>
>>>>> "Each argument shall have a type such that its value may be
>>>>> assigned
>>>>> to an object with the unqualified version of the type of its
>>>>> corresponding parameter."
>>>>>
>>>>> The phrase "unqualified version of the type" can't surely be
>>>>> intended to
>>>>> mean the recursive removal of all type qualifiers. Elsewhere it
>>>>> simply
>>>>> means that the "top level" qualifiers are removed. If that's the
>>>>> right
>>>>> interpretation, the call to bar should be a constraint violation.
>>>>>
>>>>
>>>> Why? The result of the call to 'foo' yields a 'struct toto', which is
>>>> not qualified (and also isn't an lvalue).
>>>
>>> I think Ben's point is the *parameter* value may be *assigned*, not the
>>> result of the expression.
>>>
>>> I see Sun c99, which rejects the return in foo, also accepts bar.
>>>

>>
>> As in, a constraint violation against 6.5.16p2?

>
> Yes.
>
>> If so, why would that constraint apply?

>
> Because of 6.5.2.2 p4, if you take the word "assigned" literally.
>


Did you mean if you _don't_ take it literally? I don't see "assigned"
as a defined term, so I assume it's plain English. The Committee
Response for Defect Report #212 includes "assigned" when their code
shows initialization.

Considering 6.9.1p11, does its use of "assigned" make sense if one of
the parameters was 'const'-qualified? I don't think so.

>> There are still constraints in 6.5.16.1p1... As asked elsethread, why pick out "modifiable" and not "lvalue"? Do we need to worry about the constraints and semantics of 6.5.16.2? Isn't backing up to 6.5.16 backing up too far?


6.7.3p4:

"The properties associated with qualified types are meaningful only
for expressions that are lvalues.132)"

The "object" in "object with the unqualified version of the type of its
corresponding parameter" seems to me to be a theoretical object and not
an lvalue. It's not the parameter, for example, because the parameter
could have a different type. So I don't grok a constraint violation
against 6.5.16p2.

--
- 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-24-2013
Ben Bacarisse <(E-Mail Removed)> writes:

> Tim Rentsch <(E-Mail Removed)> writes:
>
>> Ian Collins <(E-Mail Removed)> writes:
>>
>>>> [snip]
>>>>
>>> I just tried this code:
>>>
>>> #include <stdio.h>
>>>
>>> struct toto { const int i; const float f; const void *p; };
>>>
>>> struct toto foo(int i, float f, void *p)
>>> {
>>> return (struct toto){i, f, p};
>>> }
>>>
>>> int main(void)
>>> {
>>> float f = foo( 1,2,NULL).f;
>>> }
>>>
>>> and gcc (-Wall -std=c99 -pedantic) compiles it, but Sun c99
>>> (which tends to be both strict and conforming) rejects it
>>> with one error:
>>>
>>> "x.c", line 7: left operand must be modifiable lvalue: op "="
>>>
>>> where line 7 is the function return.

>>
>> I believe gcc has it right here. Even the error message from
>> the Sun compiler suggests they were thinking of this like a
>> kind of assignment, which it certainly isn't in this case.

>
> As you say elsewhere, the assignment-like nature of the return
> only kicks in when the types differ, but even when it does, I'd
> take "is converted as if by assignment" to mean that only the
> conversion and type constraints of assignment are to be
> considered.


I assume you mean >simple< assignment in the last part of that
sentence.

> (The constraints being important because they would, for example,
> prevent a function with type 'T *' returning a 'const T * value
> without a diagnostic).


I expect you're right if we're talking about what meaning was
intended. However, just in terms of what is written, it's hard
to make that stand up considering other parts of the Standard.
In both of the other two cases (those being function arguments
and initializer expressions) where there are similar concerns,
the limiting constraints are identified explicitly. So their
absence for 'return' stands out -- exceptio probat regulam in
casibus non exceptis, and all that. In all likelihood though
that was just an oversight, and a reasonable inference for
purposes of comp.lang.c is the constraints of simple assignment
are meant to apply.


> More curious to me is that gcc (in conforming mode) permits
> this:
>
> struct toto { const int i; const float f; const void *p; };
>
> struct toto foo(int i, float f, void *p)
> {
> return (struct toto){i, f, p};
> }
>
> int bar(struct toto p) { return p.i; }
>
> int main(void)
> {
> return bar(foo(1, 2, 0));
> }
>
> but 6.5.2.2 p2 says:
>
> "Each argument shall have a type such that its value may be
> assigned to an object with the unqualified version of the
> type of its corresponding parameter."
>
> The phrase "unqualified version of the type" can't surely be
> intended to mean the recursive removal of all type qualifiers.
> Elsewhere it simply means that the "top level" qualifiers are
> removed. If that's the right interpretation, the call to bar
> should be a constraint violation.
>
> [speculation about why gcc acts as it does] Anyway, either
> I'm misreading something or gcc has missed something.


Yes, your analysis looks spot on.

Here though I think we're looking at the flip side of the coin.
That is, this case also represents an oversight, only here the
result is more restrictive rather than less restrictive. It
seems unlikely that this tiny incompatibility would knowingly be
imposed just for function arguments, especially since it is
clearly a useful capability to support, and initialization
semantics are so intuitively natural. I expect it was just an
accident of how the writing happened -- provisions were put in to
handle the common cases, and this one unusual case fell through
the cracks. (And you can see how someone reading the rules for
function arguments might gloss over the distinction between the
two ways "unqualified version of the type" might be read.) In
a way it's good to see that common sense has prevailed (ie, in
gcc, and also I think another compiler was mentioned in another
posting) about what was meant here. All that remains is making
sure what's in the Standard gets updated to reflect that.
 
Reply With Quote
 
Shao Miller
Guest
Posts: n/a
 
      01-24-2013
On 1/24/2013 00:39, Tim Rentsch wrote:
>
> Here though I think we're looking at the flip side of the coin.
> That is, this case also represents an oversight, only here the
> result is more restrictive rather than less restrictive. It
> seems unlikely that this tiny incompatibility would knowingly be
> imposed just for function arguments, especially since it is
> clearly a useful capability to support, and initialization
> semantics are so intuitively natural. I expect it was just an
> accident of how the writing happened -- provisions were put in to
> handle the common cases, and this one unusual case fell through
> the cracks. (And you can see how someone reading the rules for
> function arguments might gloss over the distinction between the
> two ways "unqualified version of the type" might be read.) In
> a way it's good to see that common sense has prevailed (ie, in
> gcc, and also I think another compiler was mentioned in another
> posting) about what was meant here. All that remains is making
> sure what's in the Standard gets updated to reflect that.
>


I don't see how you can reconcile a 'const'-qualified parameter with
these two paragraphs of 6.9.1, if the "modifiable lvalue" from 6.5.16p2
has any bearing:

"10 On entry to the function, the size expressions of each variably
modified parameter are evaluated and the value of each argument
expression is converted to the type of the corresponding parameter as if
by assignment. (Array expressions and function designators as arguments
were converted to pointers before the call.)

11 After all parameters have been assigned, the compound statement
that constitutes the body of the function definition is executed."

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

Cheerily," -- Richard Harter
 
Reply With Quote
 
Keith Thompson
Guest
Posts: n/a
 
      01-24-2013
Ben Bacarisse <(E-Mail Removed)> writes:
> 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.

[...]
>> 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...


That could have broken existing code. A shamelessly contrived example:

#include <stdio.h>
#define WRAP(left, middle, right) left middle right
int main(void)
WRAP({, printf("Hello, world\n");, })

I can't think of any non-contrived use cases, but there could be some.

--
Keith Thompson (The_Other_Keith) http://www.velocityreviews.com/forums/(E-Mail Removed) <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
 
Reply With Quote
 
Ben Bacarisse
Guest
Posts: n/a
 
      01-24-2013
Tim Rentsch <(E-Mail Removed)> writes:

> Ben Bacarisse <(E-Mail Removed)> writes:

<snip>
>> More curious to me is that gcc (in conforming mode) permits
>> this:
>>
>> struct toto { const int i; const float f; const void *p; };
>>
>> struct toto foo(int i, float f, void *p)
>> {
>> return (struct toto){i, f, p};
>> }
>>
>> int bar(struct toto p) { return p.i; }
>>
>> int main(void)
>> {
>> return bar(foo(1, 2, 0));
>> }
>>
>> but 6.5.2.2 p2 says:
>>
>> "Each argument shall have a type such that its value may be
>> assigned to an object with the unqualified version of the
>> type of its corresponding parameter."
>>
>> The phrase "unqualified version of the type" can't surely be
>> intended to mean the recursive removal of all type qualifiers.
>> Elsewhere it simply means that the "top level" qualifiers are
>> removed. If that's the right interpretation, the call to bar
>> should be a constraint violation.
>>
>> [speculation about why gcc acts as it does] Anyway, either
>> I'm misreading something or gcc has missed something.

>
> Yes, your analysis looks spot on.
>
> Here though I think we're looking at the flip side of the coin.
> That is, this case also represents an oversight, only here the
> result is more restrictive rather than less restrictive. It
> seems unlikely that this tiny incompatibility would knowingly be
> imposed just for function arguments, especially since it is
> clearly a useful capability to support, and initialization
> semantics are so intuitively natural. I expect it was just an
> accident of how the writing happened -- provisions were put in to
> handle the common cases, and this one unusual case fell through
> the cracks. (And you can see how someone reading the rules for
> function arguments might gloss over the distinction between the
> two ways "unqualified version of the type" might be read.) In
> a way it's good to see that common sense has prevailed (ie, in
> gcc, and also I think another compiler was mentioned in another
> posting) about what was meant here. All that remains is making
> sure what's in the Standard gets updated to reflect that.


One option would be to define function calling in terms of
initialization but that may have unforeseen consequences. Something like

"Each argument shall have a type such that its value may be used as a
single expression initializer of its corresponding parameter."

and later:

"In preparing for the call to a function, the arguments are evaluated,
and each parameter is initialized using the value of the corresponding
argument."

At first glance, all the right wording is there, at least for scalar
types:

11 The initializer for a scalar shall be a single expression,
optionally enclosed in braces. The initial value of the object is
that of the expression (after conversion); the same type
constraints and conversions as for simple assignment apply, taking
the type of the scalar to be the unqualified version of its
declared type."

but then paragraph 13 surprised me a little:

13 The initializer for a structure or union object that has automatic
storage duration shall be either an initializer list as described
below, or a single expression that has compatible structure or
union type. In the latter case, the initial value of the object,
including unnamed members, is that of the expression.

This is from the "semantics" section, so for structure and union types
there is no constraint violation if the types are not compatible.
Whilst this is an odd anomaly of initialization, it's probably
unacceptable for function calls -- you really want to mandate a
diagnostic when the types don't match.

Also, on a side issue, the wording about disregarding the type's
qualifiers is also missing. Obviously

const struct s = (struct s){0};

is supposed to be permitted, so is "struct s" a "compatible structure or
union type"? Probably. It's not a compatible type, but the extra words
might be there to suggest that it's only the structural part that is to
be considered. I'd rather that were more explicit, though.

--
Ben.
 
Reply With Quote
 
Tim Rentsch
Guest
Posts: n/a
 
      01-24-2013
Ben Bacarisse <(E-Mail Removed)> writes:

> Tim Rentsch <(E-Mail Removed)> writes:
>
>> Ben Bacarisse <(E-Mail Removed)> writes:

> <snip>
>>> More curious to me is that gcc (in conforming mode) permits
>>> this:
>>>
>>> struct toto { const int i; const float f; const void *p; };
>>>
>>> struct toto foo(int i, float f, void *p)
>>> {
>>> return (struct toto){i, f, p};
>>> }
>>>
>>> int bar(struct toto p) { return p.i; }
>>>
>>> int main(void)
>>> {
>>> return bar(foo(1, 2, 0));
>>> }
>>>
>>> but 6.5.2.2 p2 says:
>>>
>>> "Each argument shall have a type such that its value may be
>>> assigned to an object with the unqualified version of the
>>> type of its corresponding parameter."
>>>
>>> The phrase "unqualified version of the type" can't surely be
>>> intended to mean the recursive removal of all type qualifiers.
>>> Elsewhere it simply means that the "top level" qualifiers are
>>> removed. If that's the right interpretation, the call to bar
>>> should be a constraint violation.
>>>
>>> [speculation about why gcc acts as it does] Anyway, either
>>> I'm misreading something or gcc has missed something.

>>
>> [snip] this case also represents an oversight, [and it was
>> expected that such cases would be allowed.] All that remains
>> is making sure what's in the Standard gets updated to reflect
>> that.

>
> One option would be to define function calling in terms of
> initialization but that may have unforeseen consequences.
> Something like
>
> "Each argument shall have a type such that its value may be
> used as a single expression initializer of its corresponding
> parameter."
>
> and later:
>
> "In preparing for the call to a function, the arguments are
> evaluated, and each parameter is initialized using the value
> of the corresponding argument."


Yes, I think something along these lines is the right approach.

> At first glance, all the right wording is there, at least for
> scalar types:
>
> 11 The initializer for a scalar shall be a single expression,
> optionally enclosed in braces. The initial value of the
> object is that of the expression (after conversion); the
> same type constraints and conversions as for simple
> assignment apply, taking the type of the scalar to be the
> unqualified version of its declared type."
>
> but then paragraph 13 surprised me a little:
>
> 13 The initializer for a structure or union object that has
> automatic storage duration shall be either an initializer
> list as described below, or a single expression that has
> compatible structure or union type. In the latter case,
> the initial value of the object, including unnamed
> members, is that of the expression.
>
> This is from the "semantics" section, so for structure and union
> types there is no constraint violation if the types are not
> compatible. Whilst this is an odd anomaly of initialization,
> it's probably unacceptable for function calls -- you really want
> to mandate a diagnostic when the types don't match.


Surely this anomaly, as you call it, is just another oversight,
and the wording in p13 should be changed to be in line with
what everyone expects. That would eliminate any incongruity
with treating function arguments as initializing expressions,
and also remedy the point you identify next.

> Also, on a side issue, the wording about disregarding the type's
> qualifiers is also missing. Obviously
>
> const struct s = (struct s){0};
>
> is supposed to be permitted, so is "struct s" a "compatible
> structure or union type"? Probably. It's not a compatible type,
> but the extra words might be there to suggest that it's only the
> structural part that is to be considered. I'd rather that were
> more explicit, though.


One approach to the second problem would be to use type categories
instead of compatible type, eg,

13 The initializer for a structure or union object that has
automatic storage duration shall be either an initializer
list as described below, or a single expression having
the same type category as that of the structure or union
object. In the latter case, the initial value of the object,
including unnamed members, is that of the expression.

However, that wording by itself doesn't address the need for a
constraint violation for expressions not having the same type
category. (Note: surely it was intended that such cases be
constraint violations -- wasn't it?) Another idea is to change
p13 to have wording along the lines of what is used in p11.
Personally I kind of like the type category idea, but I think
using p11-like wording is more consistent, and so more likely
to be adopted if a change were proposed.
 
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