list(APPEND) on function argument yields bad_alloc exception
Introduction
By accident it discovered a serious performance-penalty when using list(APPEND)
to append data to a function
argument that is exactly named as the variable it points to.
How to reproduce
Try to run the following CMake script (called test-performance-hit.cmake
):
function( list_append_func mylist )
foreach( num RANGE 10000 )
message( STATUS "list_append_func: ${num}" )
list( APPEND ${mylist} "${num}" )
endforeach()
endfunction()
function( set_replace_func mylist )
foreach( num RANGE 10000 )
message( STATUS "set_append_func: ${num}" )
set( ${mylist} "${${mylist}};${num}" )
endforeach()
endfunction()
macro( list_append_macro mylist )
foreach( num RANGE 10000 )
message( STATUS "list_append_macro: ${num}" )
list( APPEND ${mylist} "${num}" )
endforeach()
endmacro()
macro( set_replace_macro mylist )
foreach( num RANGE 10000 )
message( STATUS "set_replace_macro: ${num}" )
set( ${mylist} "${${mylist}};${num}" )
endforeach()
endmacro()
function( list_append_with_init_func mylist )
set( ${mylist} "" )
foreach( num RANGE 10000 )
message( STATUS "list_append_init_func: ${num}" )
list( APPEND ${mylist} "${num}" )
endforeach()
endfunction()
function( list_append_with_unset_func mylist )
unset( ${mylist} )
foreach( num RANGE 10000 )
message( STATUS "list_append_with_unset_func: ${num}" )
list( APPEND ${mylist} "${num}" )
endforeach()
endfunction()
if (TESTCASE EQUAL 1)
list_append_func( mylist )
elseif (TESTCASE EQUAL 2)
set_replace_func( mylist )
elseif( TESTCASE EQUAL 3)
list_append_macro( mylist )
elseif( TESTCASE EQUAL 4)
set_replace_macro( mylist )
elseif( TESTCASE EQUAL 5)
list_append_with_init_func( mylist )
elseif( TESTCASE EQUAL 6)
list_append_with_unset_func( mylist )
else()
message( STATUS "Please set variable 'TESTCASE' to a number from the range [1 , 6]!" )
endif()
Run it with cmake -D TESTCASE=<NUM> -P test-performance-hit.cmake
and <NUM>
being a number between 1 and 6 to specifically just run that test-case.
Observation
You will notice, that test-cases 2 to 6 finish quite quickly printing the following (shortened) output:
-- list_append_func: 0
-- list_append_func: 1
-- list_append_func: 2
-- list_append_func: 3
-- list_append_func: 4
-- list_append_func: 5
...
-- list_append_init_func: 9995
-- list_append_init_func: 9996
-- list_append_init_func: 9997
-- list_append_init_func: 9998
-- list_append_init_func: 9999
-- list_append_init_func: 10000
However, test-case one will slow down dramatically after ca. 25 loop-cycles and then crash with output similar to the following:
-- list_append_func: 0
-- list_append_func: 1
-- list_append_func: 2
-- list_append_func: 3
-- list_append_func: 4
-- list_append_func: 5
-- list_append_func: 6
-- list_append_func: 7
-- list_append_func: 8
-- list_append_func: 9
-- list_append_func: 10
-- list_append_func: 11
-- list_append_func: 12
-- list_append_func: 13
-- list_append_func: 14
-- list_append_func: 15
-- list_append_func: 16
-- list_append_func: 17
-- list_append_func: 18
-- list_append_func: 19
-- list_append_func: 20
-- list_append_func: 21
-- list_append_func: 22
-- list_append_func: 23
-- list_append_func: 24
-- list_append_func: 25
-- list_append_func: 26
-- list_append_func: 27
-- list_append_func: 28
-- list_append_func: 29
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Aborted
Interpretation of the problem
The first problem is that within the function
s the expression ${mylist}
expands to mylist
which is the same name as the function argument. Therefore appending to these variable does not work as expected and results in empty/wrong results.
Note: Giving another variable name
mylist1
to the calls of these functions/macros will resolve the problem because${mylist}
will then resolve tomylist1
.
However, the more problematic problem (besides the wrong result-calculation in these special cases) is that list(APPEND)
-ing to a function
argument that evaluates to its own name somehow results in some recursion that seems to be quite memory-heavy and -although not endless- will eventually crash with out-of-memory.