Is function declared as __attribute__ ((pure)) allowed to return newly constructed std::string

761 Views Asked by At

In gnu world of C/C++ with GCC compiler there is Common function attribute "pure" (which is like "const" attribute, but with less restrictions):

Many functions have no effects except the return value and their return value depends only on the parameters and/or global variables. ... Some common examples of pure functions are strlen or memcmp. ... The pure attribute imposes similar but looser restrictions on a function’s defintion than the const attribute: it allows the function to read global variables. ... Because a pure function cannot have any side effects it does not make sense for such a function to return void.

Is it allowed to pure function to call any C++ STL constructors like std::string or std::vector? For example, is this code legal and why it is not? (Will it be legal with __attribute__((const))?)

#include <string>
#include <cstdio>
__attribute__((pure)) std::string GetFilesystemSeparator(int unixvar) {
   if(unixvar) {
      return "/";   
   } else {
      return "\\";
   }
}

int main() {
    std::string dirname1="dir1";
    std::string dirname2="dir2";
    std::string filename="file";
    int unixvar;
    std::string path;
    puts("Unix style:");
    unixvar = 1;
    path=dirname1 + GetFilesystemSeparator(unixvar) + dirname2 +  GetFilesystemSeparator(unixvar) + filename;
    puts(path.c_str());


    puts("Not Unix style:");
    unixvar = 0;
    path=dirname1 + GetFilesystemSeparator(unixvar) + dirname2 +  GetFilesystemSeparator(unixvar) + filename;
    puts(path.c_str());
    return 0;
}

g++ pure.cc -o pure -fverbose-asm --save-temps
clang++ pure.cc -o pure1 -O3 -save-temps

There are some calls to complex std::sting constructor, which may allocate memory and write to some global variables which are used to manage free and allocated memory:

less  pure.s
...
_Z22GetFilesystemSeparatorB5cxx11i:
    call    _ZNSaIcEC1Ev@PLT        #
    call    _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EPKcRKS3_@PLT    #

For example, after changing the length of "/" and "\\" constants to 100 chars, I have new and malloc(101) calls from the constructor:

ltrace -e '*@*' ./pure3
...
libstdc++.so.6->strlen("////////////////////////////////"...)                         = 100
libstdc++.so.6->_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE12_M_constructIPKcEEvT_S8_St20forward_iterator_tag(0x7ffc7b66a840, 0x558899f74570, 0x558899f745d4, 0 <unfinished ...>
libstdc++.so.6->_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_createERmm(0x7ffc7b66a840, 0x7ffc7b66a6b0, 0, 0 <unfinished ...>
libstdc++.so.6->_Znwm(101, 0x7ffc7b66a6b0, 0, 0 <unfinished ...>
libstdc++.so.6->malloc(101)                                                           = 0x55889bef0c20
<... _Znwm resumed> )                                                                 = 0x55889bef0c20
<... _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_createERmm resumed> )   = 0x55889bef0c20
libstdc++.so.6->memcpy(0x55889bef0c20, "////////////////////////////////"..., 100)    = 0x55889bef0c20
1

There are 1 best solutions below

0
On

This documentation has been updated or you quoted it incorrectly. Now the part you quoted speaks about "observable side effects", instead of "side effects".

What a pure function means:

The pure attribute prohibits a function from modifying the state of the program that is observable by means other than inspecting the function’s return value.

tells GCC that subsequent calls to [the function] with the same string can be replaced by the result of the first call provided the state of the program observable by [the function] does not change in between.

So the question you should ask is not "is this legal", but "if the second call is removed by the optimizer, does the behavior of my program change". If the answer is "no", you can use the attribute. The behavior of pure and const functions is not enforced by the compiler, it is just a hint to the optimizer.

Does returning std::string have observable side effects?

std::string allocates memory. You could take the address of the internal buffer, and put it into a global variable. In such case the side effect would be observable.

A const function would not read global variables, so this side effect would not be observable in the function itself. But you could print the address of the internal buffer after calling the function, and see whether you have two distinct objects.

If you used the string like you normally would, instead of trying to break your program, the side effect would not be observable.