Intel® C++ Compiler
Community support and assistance for creating C++ code that runs on platforms based on Intel® processors.
7942 Discussions

Bug: lambda capture-by-value with copy constructor fails

void-1
Beginner
792 Views
I am evaluating Intel C++ Professional 11.1 for Windows (11.1.038). I have encountered a serious (and easy to reproduce) bug in the new (C++0x) lambda feature.

Here is a minimal reproduction:

[cpp]#include 

int main() {
std::string wrong = "Goodbye, World!";
[wrong](){}();
return 0;
}[/cpp]

This trivial program, compiled in debug (/Od) or release mode with /Qstd=c++0x, aborts on the line containing the lambda.

Further experimentation reveals that capture-by-value with [=] or [var] works fine for built-in types (int, char*) but not for anything with a nondefault copy constructor. The following program gives some insight into what's wrong:

[cpp]#include 
#include

using namespace std;

class Test {
public:
Test(int x)
: value(x)
{
cout << "Test " << value << " created at "
<< this << endl;
}
~Test() {
cout << "Test " << value << " destroyed at "
<< this << endl;
}
Test(const Test& r)
: value(r.value)
{
cout << "Test " << value << " copied from "
<< &r << " to new " << this << endl;
}
void operator=(const Test& r) {
value = r.value;
cout << "Test " << value << " assigned from "
<< &r << " to " << this << endl;
}
private:
int value;
};

int main() {
Test test(1);
[test](){}();
return 0;
}[/cpp]

The output from this program (in debug mode) on my computer is:

Test 1 created at 002FFCE4
Test 3144932 copied from 002FFCDC to new 002FFCEC
Test 3144932 destroyed at 002FFCEC
Test 1 destroyed at 002FFCE4

The copy constructor of Test is called with the wrong reference (pointing to some location on the stack which doesn't contain the original test object), and thus creates a completely invalid new object. It's easy to see why this results in Bad Things when copying std::string in the first example.

Microsoft Visual Studio 2010 Beta 1 runs these programs correctly, yielding for the second one:

Test 1 created at 003FFA18
Test 1 copied from 003FFA18 to new 003FF940
Test 1 destroyed at 003FF940
Test 1 destroyed at 003FFA18

Thanks for looking into this!
--void

(edited to report success on VS 2010, and to remove syntax that is accepted by Intel but not standard)
0 Kudos
11 Replies
Zenny_Chen
Beginner
792 Views
Mark first.

I shall do some more tests about this.

0 Kudos
Zenny_Chen
Beginner
792 Views
Here is a simpler example:
[cpp]#include   
using namespace std;   
  
class Test {

public:

    Test(int x) : value(x)    
    {        
    }

    Test(const Test &r) : value(r.value)    
    {
        cout << "source value is: " << r.value << endl;
    }   
   
private:   
    int value;   
};   
  
int main()
{
    Test test(1);   

    [test](){}();   

    return 0;   
}  
[/cpp]

Apparently, the copy constructor is called but the reference is invalid.

It is possibly a bug.
0 Kudos
Zenny_Chen
Beginner
792 Views
An MSN pal of mine tried it on Windows XP with Visual Studio 2010 but got a right answer.
0 Kudos
void-1
Beginner
792 Views
Zenny,

Thanks for the help!

I updated my original post to report the results from the same program under VS 2010 (which works correctly). I have also noticed that the compiler generated function object created from a lambda itself has a user defined copy constructor (or the equivalent) that suffices to trigger the bug:

[cpp]#include 
#include 

using namespace std;

int main() {
	int number = 10;
	auto f = [=]() {
		cout << "number is " << number << endl;
	};
	f();
	auto g = [=]() {
		f();
	};
	g();

	return 0;
}[/cpp]
This program produces this (incorrect) output on Intel:
number is 10
number is 4193700

and this (correct) output on VS 2010 Beta 1:
number is 10
number is 10

I believe this is the same bug, and that my original bug report will be most useful for reproducing it, but a program like this one belongs in the test suite for the compiler as well. (At the risk of being snarky, DOES Intel test its compiler? It's a little hard for me to imagine a serious test suite for lambda functions that doesn't trigger this problem!)

I'm still hoping for a comment from Intel.

--void
0 Kudos
void-1
Beginner
792 Views
The recently released 11.0.046 (build date 20090903) compiler still exhibits the (alleged) bug.

I would try to submit it to Intel Premier Support, but unfortunately the website does not work! ("HTTP 500 - Internal Server Error")
0 Kudos
JenniferJ
Moderator
792 Views
Quoting - void-1
The recently released 11.0.046 (build date 20090903) compiler still exhibits the (alleged) bug.

Thanks for posting the issue report here. I've filed a bug report (DPD200140177) to the compiler engineer. Once it's fixed, I'll post the news to this thread.

Jennifer
0 Kudos
void-1
Beginner
792 Views
Jennifer,

Thanks. For what it's worth, in my last post I meant 11.1.046.

--void
0 Kudos
Dmitry_Vyukov
Valued Contributor I
792 Views

Have the same problem on 11.1.071 Windows

0 Kudos
jimdempseyatthecove
Honored Contributor III
792 Views

And have the same problem in 11.1.072 Windows x64 - compiles fine, wrong address for object passed

From running through the Dissassembly window the following sequence of events occures

1) Lambda capture context block space reserved on stack
2) for simple variables [=] copies fine
3) for objects passed by [=] and with copy constructor T(const volatile T& refT) is called for the copy (it would be nice if this where documented. However it is called in error.

a) the address within the Lambda capture context block is correct (IOW target of the ctor is ok)
b) the address of the refT (the reference) is copied to a stack temporary
c) the address of the stack temporary is pass to the copy constructor (ctor then trashes memory)
d) the address of the stack temporary is inserted into the Lambda capture context block and passed (later) into the Lambda function.

Effectively the copy constructor receives T&& refrefT

The above may help your developers in tracking down the problem

Also, I surmize the first reference is copied to the stack temp as a byproduct of the volatile type. The volatile type should cause the compiler to generate code the _read_ a fresh copy of the reference from memory when passing to copy constructor and NOT write copy to memory and then re-read that. Although in this case it is issuing something like

lea rax,[loc]

when it should be issuing

mov rax, [loc]

The memory temp should not be required/used in this case.

Seperate, but related issue

The Lambda capture context block needs a copy constructor.

Reason being, the Lambda capture context block is constructed on the current stack.
When you need to run an asynchronous task that may not begin to run until _after_ the scope in which the Lambda capture context block was constructed. Then you cannot use the original Lambda capture context block, but you can use a copy of this block, provided you make the copy (which is QED using a template). Under the current circumstance: Address of Lambda capture context blockconstructed on the current stack passed to asynchronous task, when task runs after dtor of Lambda capture context blockconstructed on _then_ current stack, the task sees a corrupted Lambda capture context block.

Jim Dempsey
www.quickthreadprogramming.com

0 Kudos
jimdempseyatthecove
Honored Contributor III
792 Views

Jennifer,

RE:

The Lambda capture context block needs a copy constructor.

Reason being, the Lambda capture context block is constructed on the current stack.

In running additional test the copy constructor/operator is working properly same with the dtor on the copy of the Lambda capture block.

Therefor this is no problem.

Jim Dempsey

0 Kudos
Om_S_Intel
Employee
792 Views

This issue has been fixed in the latest Intel C++ Composer XE. The compiler is available from our registration and download center.

Thanks,

Om

0 Kudos
Reply