A Collection of Useful C++ Classes for Signal Processing

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

aciddose wrote:For example:

y++ = (x++ = (y++ = x++) * x++ * y++);
Judging from your example, with several assignments, I suspect that your view point is that "=" is simply part of the expression, therefore the warning on expression evaluation holds for the entire statement. So I'll address that:

This is a compound statement—statements combined within statements. Compound statements are a convenience for the programmer, but individually they must obey the rules. In the case of your example, all of the statements are illegal.

They are illegal because "x++" and "y++" are not lvals. It's not a matter of these statements not executing correctly—the compiler will simply not compile them. And it will tell you why—the old way is to tell you that they aren't lvals (I see that Xcode now says it in more plain English—"Expression is not assignable", with "y++" highlighted, starting with the innermost one).

However, "*dest++" is an lval. And its behavior for assignment ("*dest++ =...") is defined explicitly in C/C++. It means "assign the value of the expression on the right of the equal sign to the location pointed to by dest, then increment dest". It's not a C compiler if it doesn't do this.
My audio DSP blog: earlevel.com

Post

Sorry, I wanted to avoid all the dereferences, what I intended was something more like this:

*y++ = (*x++ = (*y++ = *x++) * *x++) * *y++;

How do you deal with operator overloading?

Here is a fun example:

Code: Select all

struct evil_t
{
	evil_t()
	{
	}

	evil_t(int init) : v(init)
	{
	}

	int operator +(int x)
	{
		printf("%i + %i", v, x);
		int result = v += x;
		printf(" : result %i written\n", result);
		return result;
	}

	int operator *(int x)
	{
		printf("%i * %i", v, x);
		int result = (v + x) * (1 + v * x);
		printf(" : result %i\n", v, x, result);
		return result;
	}

	int operator ++(int)
	{
		printf("++");
		int result = 1;
		printf(" : result %i\n", result);
		return result;
	}
	
	int operator =(int x)
	{
		printf("=");
		int result = v * x;
		printf(" : result %i\n", result);
		return result;
	}

	operator int()
	{
		return v;
	}

	int v;
};

int commit_evil_atrocities()
{
	evil_t v[8] = 
	{
		evil_t(3), 
		evil_t(1),
		evil_t(3), 
		evil_t(3),
		evil_t(7),
		evil_t(5),
		evil_t(5),
		evil_t(5),
	};
	evil_t *x = &v[0];
	evil_t *y = &v[1];

	printf("committing evil atrocities\n");

	*y++ = (*x++ = (*y++ = *x++) * *x++) * *y++;

	int result = *y;
	printf("result: %i\n", result);
	printf("v[8]\n{\n");
	for (int i = 0; i < 8; i++) {
		printf("\t%i,\n", v[i]);
	}
	printf("}\n");

	return result;
}
Output:

Code: Select all

committing evil atrocities
3 * 3 : result 3
= : result 180
= : result 1620
result: 7
v[8]
{
        3,
        3,
        3,
        3,
        7,
        5,
        5,
        5,
}
Without braces:

Code: Select all

*y++ = *x++ = *y++ = *x++ * *x++ * *y++;

Code: Select all

committing evil atrocities
1 * 3 : result 1
= : result 48
= : result 144
= : result 432
result: 7
v[8]
{
        3,
        1,
        3,
        3,
        7,
        5,
        5,
        5,
}
Last edited by aciddose on Mon Mar 09, 2015 11:10 pm, edited 1 time in total.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

aciddose wrote:Sorry, I wanted to avoid all the dereferences, what I intended was something more like this:

*y++ = (*x++ = (*y++ = *x++) * *x++) * *y++;
Well, I'm just saying that there is only one way to evaluate the original statement in question. The compiler may warn, but there is only one answer. The above has multiple modifications to x and to y. The original statement passed the value that dest pointed to, to a function, and assigned the returned value to what dest pointed to, and incremented dest. Where's the danger? The generated code couldn't increment dest until the assignment is made, which means that the function has already been called and returned.

I'm totally open to seeing a compiler spit out code that doesn't do it in that order, but short of that, I'm skeptical that any compliant compiler will. Maybe I'm missing something; if so, feel free to point it out.
How do you deal with operator overloading?
Haha, well, you've heard the old saying, "C gives you the rope with which you can hang yourself; C++ ties the knot."?
My audio DSP blog: earlevel.com

Post

I've never heard that saying.

I've heard "Unix doesn't prevent you from doing something stupid, because that would also prevent you from doing something clever."

"I'm skeptical that any compliant compiler will."

Well, except for the one that was used when this bug was reported.

As far as C, any line that writes to more than one destination value is going to result in unpredictable behavior.

Since the increment operator (or any operator) can write, this means you need to be aware of exactly what the consequences of anything you write may be.

In the case of:

*p++ = function(*p, ...)

It is undefined whether p will be incremented before or after its value is passed to the function. Some compilers will do it one way, some will do it another, and others still may do things we might never imagine.

So, that makes this code bad/buggy as what it does in all cases is unknowable. Such code should never be written.

I've never used this sort of code before because I'd use an increment/decrement only as part of a loop only when the overhead of an index would make up a significant (say > 10%) portion of the operation inside the loop.

(Considering this, wouldn't a for loop and index make more sense in this particular case? Unless the operation inside the function call is inlined by the compiler the function call itself will take at least the same amount of time as the index/loop.)

I also am not bothered by a few additional lines, so I have no motivation to combine multiple operations into a single statement.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

aciddose wrote:Well, except for the one that was used when this bug was reported.
Yeah, strange, but...I don't see where we've yet heard what compiler that was, and I don't see any compiler output. And after knowing those two things, I'd be curious of what compiler switches were set to allow this to happen. Next, I'd like to know what the execution benefit is—small code? faster code? I doubt it.

If the compiler really increments the pointer before the call, I have a hard time believing that it doesn't break an awful lot of code. It would be an almost predatory action—looking to screw people for missing a somewhat obscure detail that the compiler will not stop you from doing.

Not that it would be the first time I'd ever seen a compiler purposely do something ridiculous. Many years ago, I had an online argument with a person at THINK Technologies over Think C. They had mis-interpreted the C standard rules (in my opinion, not theirs, obviously). I believe that it was when returning a 32-bit int in the manner "return val1 * val2", it would perform the 32-bit computation, truncate it to 16-bit, extend it back to 32-bit, and return it. Incredibly idiotic, and you could only overcome it by using a temporary variable to do the computation, then return that. They cited a specific sentence in the spec that they completely misunderstood. They knew it would break code, but this was at a time when it was important that all compilers should produce code that gives identical results, and they felt they were following the letter of the law (what they thought the law said, anyway—it had to do with a misunderstand about what was meant by "int", at a time when the Unix-world spec writers assumed that int was always the natural size of the processor). I stopped using the compiler, feeling that it had been taken over by bureaucrats, and they were bought by Symantec.
I also am not bothered by a few additional lines, so I have no motivation to combine multiple operations into a single statement.
Well, there's "not combining" and there's the opposite—factoring. I'm sure you don't turn "x = 2 * y + z + 5" into "x = 2 * y; x = x + z; x = x + 5;" I don't view the original line of code as looking like a combined statement, if that's what you mean.
My audio DSP blog: earlevel.com

Post

That isn't what I mean, because that is a single computation that has a single write.

Now if that were in c++ and any of the variables were not built-in types (known to the author), for example if this were part of a template function, then absolutely you must specify exactly the order of operations you want by using separate statements.

Code: Select all

template <typename T>
T function(T y, T z)
{
T a = T(2) * y;
T b = z + T(5);
T x = a + b;
return x;
}
Absolutely, although due to the assignment potentially being overridden as the author of a template function like this one you have limited control. At least overrides of temporary result add/multiply should function as intended with this implementation.

Example of such a template function:

Say we have a string class where a multiply creates spaced duplicates of the string (say, stripping redundant white-space) while an addition concatenates to the string. The constructor given a numeric value converts to the textual representation based upon the locale.

In such a case:

Code: Select all

function(y = "hello", z = "world");
{
T a = T(2) * y;
// assuming the multiplication operator is intelligent enough to
// convert a textual back to numeric
// a = "hello hello"
T b = z + T(5);
// b = "worldfive"
T x = a + b;
// x = "hello helloworldfive"
return x;
}
edit:

Actually now I realize you can avoid the issue of the assignment operator.

Code: Select all

template <typename T>
T function(T y, T z)
{
T a(T(2) * y);
T b(z + T(5));
T x(a + b);
return x;
}
If you're a C guy, I'm sure this makes you scream.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

earlevel wrote:Not that it would be the first time I'd ever seen a compiler purposely do something ridiculous. Many years ago, I had an online argument with a person at THINK Technologies over Think C. They had mis-interpreted the C standard rules (in my opinion, not theirs, obviously). I believe that it was when returning a 32-bit int in the manner "return val1 * val2", it would perform the 32-bit computation, truncate it to 16-bit, extend it back to 32-bit, and return it. Incredibly idiotic, and you could only overcome it by using a temporary variable to do the computation, then return that. They cited a specific sentence in the spec that they completely misunderstood. They knew it would break code, but this was at a time when it was important that all compilers should produce code that gives identical results, and they felt they were following the letter of the law (what they thought the law said, anyway—it had to do with a misunderstand about what was meant by "int", at a time when the Unix-world spec writers assumed that int was always the natural size of the processor). I stopped using the compiler, feeling that it had been taken over by bureaucrats, and they were bought by Symantec.
If the two values were 16bits ints, that is what is expected, isn't it? Just like adding 2 16ints can also lead to an overflow?
I think the behavior is indeed undefined. *dest++ returns a temporary lvalue that will receive the assignment. It is valid to evaluate this part, and then evaluate the ather section.
Which also why I'm not using pointers in that way in my library, as compiler are now smart enough to know that you are iterating over an array.

Post

Miles1981 wrote:
earlevel wrote:Not that it would be the first time I'd ever seen a compiler purposely do something ridiculous. Many years ago, I had an online argument with a person at THINK Technologies over Think C. They had mis-interpreted the C standard rules (in my opinion, not theirs, obviously). I believe that it was when returning a 32-bit int in the manner "return val1 * val2", it would perform the 32-bit computation, truncate it to 16-bit, extend it back to 32-bit, and return it. Incredibly idiotic, and you could only overcome it by using a temporary variable to do the computation, then return that. They cited a specific sentence in the spec that they completely misunderstood. They knew it would break code, but this was at a time when it was important that all compilers should produce code that gives identical results, and they felt they were following the letter of the law (what they thought the law said, anyway—it had to do with a misunderstand about what was meant by "int", at a time when the Unix-world spec writers assumed that int was always the natural size of the processor). I stopped using the compiler, feeling that it had been taken over by bureaucrats, and they were bought by Symantec.
If the two values were 16bits ints, that is what is expected, isn't it? Just like adding 2 16ints can also lead to an overflow?
No, the arguments weren't 16-bit ints. I wouldn't be using it as an example of idiotic compiler behavior if that were the case. It had to do with the spec saying that intermediate results would assume int—but this was written with the assumption that ints were the natural size of the processor ALU, and assumed that that any promotion would be upward, whereas Think C defined int as 16-bit for the 32-bit 68k family, and the "promotion" would be downward. I think there might have been another nuance, like one of the values being a constant. The thing is that they knew it would break an otherwise natural-looking computation, but they thought they were causing it to fail with the same results as other compliant compilers. Anyway, it was 25 years ago or so, and the compiler went from being the darling of Mac programmers to unused pretty quickly.
I think the behavior is indeed undefined. *dest++ returns a temporary lvalue that will receive the assignment. It is valid to evaluate this part, and then evaluate the ather section.
As I said, I'm pointing out that a compiler that makes a copy of the dereferenced pointer, increments the pointer, then dereferences the incremented pointer, is idiotic. I didn't say it was defined. I'm still curious to see this compiler named.
My audio DSP blog: earlevel.com

Post

earlevel wrote:As I said, I'm pointing out that a compiler that makes a copy of the dereferenced pointer, increments the pointer, then dereferences the incremented pointer, is idiotic.
Out of interest, what would you expect to be the result of:

Code: Select all

*(++p)=some_func(*p)
Do you think some_func should be evaluated using the incremented value of p, or the un-incremented value?
Should the order of left-hand and right-hand side evaluation (with side-effects) be different when using ++p as opposed to p++ ? How is the compiler to know what you expect?
Yes, it might be clearer if it was tied down in the spec, but it isn't tied down - so the solution is not to write code like this.

Post

kryptonaut wrote:
earlevel wrote:As I said, I'm pointing out that a compiler that makes a copy of the dereferenced pointer, increments the pointer, then dereferences the incremented pointer, is idiotic.
Out of interest, what would you expect to be the result of:

Code: Select all

*(++p)=some_func(*p)
Do you think some_func should be evaluated using the incremented value of p, or the un-incremented value?
Should the order of left-hand and right-hand side evaluation (with side-effects) be different when using ++p as opposed to p++ ? How is the compiler to know what you expect?
Yes, it might be clearer if it was tied down in the spec, but it isn't tied down - so the solution is not to write code like this.
Guys...I'm just speaking to the specific issue that I'm speaking to. Please don't keep presenting me with ambiguous examples and asking how I think the compiler should behave. In the particular line of code in question, the increment occurs after the assignment to *dest, and the assignment to *dest cannot be made until the expression to the right of the equals sign is evaluated. If the argument is that the spec doesn't say which order things should occur in, therefore it's the compiler's prerogative to make a copy of the pointer on the left, increment the original pointer, then carry out the rest of the work, passing the original pointer to the function but assigning it to the dereferenced pre-incremented pointer copy, that doesn't make it less idiotic. :-)
My audio DSP blog: earlevel.com

Post

You're just speaking, a lot, about something that really has nothing to do with the issue. You're giving your opinion "such a compiler is stupid" without saying anything to support this, you know like providing evidence. :)

The topic here is that the result of such a statement is undefined by the spec, which while surprising at first is quite understandable when you consider complexity and issues related to optimization.

Like I posted, just replace Unix with C: "C doesn't prevent you from doing things that are stupid as that would also prevent you from doing things that are clever."

If you want to go bitch at the guys responsible for the C or C++ standards themselves I'm sure they'll be happy to put you in your place. This isn't the place for such discussions though, the facts have already been determined. The behavior of such a statement in C or C++ is undefined. Period.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

The compiler has to calculate two things - namely a quantity and an address - and then store the quantity at the address. The assignment cannot be made until both of those things are calculated. But there is no rule to say which order they should be calculated.

The increment always occurs after the calculation of the address (since it's a post-increment), but that may be before or after the calculation of the RHS. Both calculations are course before the actual assignment.

So the two possible sequences of events are:
  • Calculate address, with side-effect of post-incrementing p
    Evaluate RHS with incremented p
    Store at un-incremented address calculated earlier
or:
  • Evaluate RHS with un-incremented p
    Calculate address, with side-effect of post-incrementing p
    Store RHS at un-incremented address
The first case is what exhibited the bug, the second case is presumably what the original author expected always to happen.

The reason for providing 'what if' examples is to try to demonstrate that what seems logical to a human in some circumstances is not so logical in others. Faced with that situation, the language specification could have forced an ordering, which might have had a negative impact on optimisation - or it could leave it unspecified, so that if the order is important it must be enforced by the programmer. Like it or not, the ordering is unspecified.
See here: http://en.cppreference.com/w/cpp/language/eval_order

Post

aciddose wrote:You're just speaking, a lot, about something that really has nothing to do with the issue. You're giving your opinion "such a compiler is stupid" without saying anything to support this, you know like providing evidence. :)

The topic here is that the result of such a statement is undefined by the spec, which while surprising at first is quite understandable when you consider complexity and issues related to optimization.

Like I posted, just replace Unix with C: "C doesn't prevent you from doing things that are stupid as that would also prevent you from doing things that are clever."

If you want to go bitch at the guys responsible for the C or C++ standards themselves I'm sure they'll be happy to put you in your place. This isn't the place for such discussions though, the facts have already been determined. The behavior of such a statement in C or C++ is undefined. Period.
Relax. You're "speaking" a lot too—I wouldn't continue to respond if I weren't being presented with examples that aren't at all like the original issue, and being asked things. (And you've twice given me quotes about the hazards of C programming, both following a similar quote that I presented in the first place. What will I will learn from this?)

I'm making a comment, not seeking out standards folks to bitch at. Stuff of the form "*x++ = x;" is not all that hare-brained, so I'm surprised if a compiler...it's not just that it doesn't do what people think it would do, but it seems to me that it would have to go out of its way to do. I can't even dream up a theory as to how the object code for any cpu would benefit from executing that in the unexpected order. (We're established that there is no defined order—I mean that obviously people sometimes write this sort of construct, and anyone with common sense can see the intent.)

I can't say whether I've *ever* written such a line of code—probably, I've been writing C for 35+ years—but I just grepped my drive with all my semi-modern and surviving code, and came up with zero. But, I did the same to capture all various bits of code that happen to be on my local disks, and found it in many places in code that is the subject of this thread.

And I found it in some STM32F4-Discovery support library files from ARM Ltd—here's one of them:

Code: Select all

      /* Loop over number of columns    
       * to the right of the pilot element */
      for (j = 0u; j < (numCols - l); j++)
      {
        /* Divide each element of the row of the input matrix    
         * by the pivot element */
        *pInT1++ = *pInT1 / in;
      }
      for (j = 0u; j < numCols; j++)
      {
        /* Divide each element of the row of the destination matrix    
         * by the pivot element */
        *pInT2++ = *pInT2 / in;
      }
I found it in someone's open source dsp library—apparently, I downloaded it years ago, don't recall looking at it ("AudioDrome", Alessandro Petrolati, 2008):

Code: Select all

	// Passes RT _input through "chorus" to RT output
	for (int i=0; i<bufferSize; i++)
		*samples++ = rev->getSignal(*samples);
OK, I found exactly one line of code that I've written—file dated January 30, 1989 (I knew if I went back far enough...this was for HyperMIDI):

Code: Select all

*velPtr++ = ((unsigned char)128 - *velPtr) & (unsigned char)0x7f;
Back to other peoples' code—I found this in a Mathematica support file, and can see via google that it's from a book, Projects in Scientific Computation, eliascoder.c:

Code: Select all

*iout++ = *iout << (BITS_PER_INT - inbits);
My audio DSP blog: earlevel.com

Post

kryptonaut wrote:The reason for providing 'what if' examples is to try to demonstrate that what seems logical to a human in some circumstances is not so logical in others.]
Fair enough, and your example was a reasonable question ("*(++p)=some_func(*p)"), I just was tired of seeing stuff like "*y++ = (*x++ = (*y++ = *x++) * *x++) * *y++;", which is far from having a logical expectation. To answer your question, if it were my kingdom, the right-hand side would be evaluated first. But it has an additional wrinkle over the original issue, since the left-hand side increment is before the assignment. In the other case, the assignment was before the increment. In any processor that I've worked with, the compiler would have to generate less efficient code to do that increment first, so I'm baffled, especially considering that I think this sort of statement is not extremely rare.

Anyway, I've said more than enough, and the only curiosity for me that remains is what compiler produced the code that increments the pointer first thing.
My audio DSP blog: earlevel.com

Post

earlevel wrote:Stuff of the form "*x++ = x;" is not all that hare-brained,
Let me just stop you there. Yes it is.

btw; I did read your entire post and yes, this sort of thing is rarely used because it's unnecessary, doesn't provide any real advantage to anyone and in fact limits the compiler's ability to optimize on top of the fact it is simply bad code which creates undefined behavior and will almost inevitably lead to some sort of bug.

I just don't see the point in observing that, the issue in this thread is whether it is in fact a bug. Yes, it is a bug.

The code I gave as an example was to demonstrate that your original idea that the assignment operator applies in any way what-so-ever to the order of operations or that the compiler makes any decision (itself, or based upon the specifications) about order of operations being left/right when encountering an assignment operator is completely incorrect.

(I assumed this myself at first, but after being corrected and re-reading the posts above mine I realized this assumption was based upon further assumptions which are entirely foolish.)

I gave my example code to show that the same issue occurs in other situations where assignment is not used, so as to justify the rule: "any statement containing multiple read/write of a single value results in undefined behavior."

This then in c++ extends to operator overloading and is a good rule to keep in mind when authoring this more complex/advanced sort of code.

The order of operations in a statement are completely undefined by the standard, and therefore any statement in which the outcome depends upon the order of operations will result in unspecified/undefined behavior.

(Most likely undefined when we're talking about incrementing pointers, as this will potentially lead to an out-of-bounds access.)

http://en.cppreference.com/w/cpp/language/ub

Also important to point out what "undefined behavior" means.

It means "the compiler can do anything it wants to do including nothing at all."
Last edited by aciddose on Mon Mar 09, 2015 11:01 pm, edited 1 time in total.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post Reply

Return to “DSP and Plugin Development”