This document is inappropriate because it mispositions and interprets other documents. Keep a record of any deficiencies.
My name is Mihara from ACCESS CO., LTD. Thank you for reading Day 4 of ACCESS Advent Calendar 2016.
C language. Recently, the shadow has faded, but a long time ago, when it came to programming on a PC, the programming language was almost an option (although it was actually C ++), and it is still active in embedded programming. There should be many people who have learned it. Above all, the grammar and mechanism are simple. You guys know about C, right?
I thought so too. Until a certain trouble. I will tell you the embarrassing story here.
We have a product that has been sold for more than 10 years while continuing to be reworked. The sale is written according to C89, so even minor C compilers for embedded devices will not cause bugs. So the port just compiles. I thought so.
However, when I compiled it with a customer-specified C compiler, I encountered a problem that the program did not proceed from a certain stage. Upon examination, I found that stronger optimization did not set the flags that are essential to the progress of the process.
The problematic part was the following code. (It is not the product as it is. It is a mock code)
/*
*This is the parent structure A.
*Have its own member flag
*And it has a child structure B inside.
*/
struct A {
int flag;
struct B child;
};
/*
*This is the child structure B.
*You can refer to the parent structure A.
*/
struct B {
struct A *parent;
};
/*
*The function that takes the pointer of child structure B is
*Before returning the return value
*Change the member flag of parent structure A
*/
int
func_b(struct B *self) {
self->parent.flag |= 0x1;
return 0x2;
}
struct A instance;
int prev_flag, bits;
/*
*All flags of parent structure A are cleared.
*Set a reference from child structure B to parent structure A.
*/
instance.flag = 0;
instance.child.parent = &instance;
/*
*See the value of the flag in parent structure A.
*/
prev_flag = instance.flag;
/*
*Function call with child structure B as an argument.
*The return value is saved in a variable.
*/
bits = func_b(&instance.child);
/*
*Set the return value of the function to the flag of parent structure A with OR.
*The flag values should accumulate to 0x3 ...
*/
instance.flag |= bits;
/*
*If you check the flag of structure A here, it is 0x2.
* func_b()The flag set in is gone!
*/
printf("0x%x\n", instance.flag);
If you set a flag in a function and then set another flag with OR after returning, the flag set in the function disappears. This does not work as intended.
The customer-specified C compiler is IAR Embedded Workbench for ARM Ver 6.7. The vendor website is widely advertised for high-speed machine language output. Isn't there a bug due to over-optimization? At first I thought so.
I thought that there might be a basis for a bug in the C compiler, and found the material "Rationale for International Standard— Programming Languages—C". /wg14/www/C99RationaleV5.10.pdf). The link was made to the C99 specifications, but the points to be referred to from now on do not show any differences from C89.
There was a description that acknowledged the behavior of the C compiler as described above.
The next paragraph is due to a misunderstanding. I apologize and will correct it. ~~~ "6.5.16 Assignment operators" in the above document specifies what kind of code causes the side effect of changing the value of a variable. Therefore, it is stipulated that side effects occur in function calls only when the function references the variable itself as an argument. ~~~
The next paragraph is due to a misunderstanding. I apologize and will correct it. ~~~ In the above code, the function is called for child structure B instead of parent structure A, so the C compiler can assume that parent structure A has no side effects. And since the previous code refers to a member of parent structure A, you can assume that its value hasn't changed since it was read earlier. ~~~
After the correction, it is the following part. "6.7.3 Type qualifiers" in the above document, page 75, lines 10-14, says:
Type qualifiers were introduced in part to provide greater control over optimization. Several important optimization techniques are based on the principle of “cacheing”: under certain circumstances the compiler can remember the last value accessed (read or written) from a location, and use this retained value the next time that location is read. (My translation) Type qualifiers were introduced as part of the control over optimization. Some important optimal or techniques are based on the principle of "caching": under certain conditions, the compiler remembers the last accessed (read or written) value at a location, and then the stored value: It may be used when the location is read out.
And on page 75, from line 20 to line 24, it is written as follows.
volatile: (quoted omitted) In the absence of this qualifier, the contents of the designated location may be assumed to be unchanged except for possible aliasing. (My translation) volatile: (...) Without this qualifier, the value at the specified location can be considered unchanged unless there is aliasing.
The code above reads the value of instance.flag before calling the function func_b (). And func_b () doesn't refer to the whole instance. Therefore, if func_b () considers that there is no aliasing to instance.flag, the value of instance.flag may reuse the value read in advance, that is, it is not necessary to re-evaluate the current instance.flag. Become.
I will quote the code again.
/*
*Since we referred to the value of the flag of parent structure A here,
*I know the value of the flag.
*/
prev_flag = instance.flag;
/*
*Since the function call is for child structure B,
*It can be considered that there are no side effects on parent structure A.
*/
bits = func_b(&instance.child);
/*
*After referring to the value of the flag of parent structure A
*Because there were no operations that caused side effects
*The C compiler has the flag of parent structure A
*You can assume that it hasn't changed
*You can reuse the value you just read
*It is forgiven.
*So in the function call
*The set flag value disappears.
*/
instance.flag |= bits;
Come to think of it, C compiler vendors are experts in the C language standard. I know everything that is allowed in the optimization of C language programs. It was embarrassing to insist that this was a bug in the C compiler.
Subsequent modifications to the program wanted to avoid modifications that required logic verification, so I assigned an address to a pointer with a volatile declaration to refer to it. I don't think it's too late because I only refer to the flag once.
The fundamental problem with the above program was that the child struct had an anomalous structure that changed the members of the parent struct. If you intend to cause side effects, you must take a variable that causes side effects as an argument.
Eh? A more fundamental problem? How to avoid the bug of not knowing the C language regulations?
It is difficult. It's a dream that all project members are familiar with the C89 and C99 rules. It's rare that even one member knows about a problem like the one above.
In reality, if you keep in mind the existence of the above document and find a behavior that seems to be a bug in the C compiler, you must first doubt yourself and hit the document.
Tomorrow's ACCESS Advent Calendar 2016 is @akegashi. Please look forward to it.
Recommended Posts