15

Is there a way in C++ to declare that a function has no side effects? Consider:

LOG("message").SetCategory(GetCategory()); 

Now suppose that the LOG macro in release builds creates a NullLogEntry object that has SetCategory() defined as an empty function. So basically the whole expression could (and should) be optimized away -- expcept that, in theory, the GetCategory() call may have some side effects, so I guess the compiler is not permitted to just throw it away.

Another example could be a function template specialization that ignores some (or all) of its arguments, yet the compiler is not allowed to save the evaluation of such arguments at the call site due to possible side effects.

Am I right? Or can compilers optimize away such calls anyway? If not, is there a way to hint the compiler that this function has no side effects, so if the return value is ignored then the whole call can be skipped?

imre
  • 1,667
  • 1
  • 14
  • 28
  • As long as the function is small enough to get inlined, the odds are very good that all code is optimized away. Obviously this is an implementation detail that you'll have to verify for yourself. – Hans Passant Jul 08 '11 at 11:44
  • @Cicada: some C++ compilers define attributes (like `pure`) which let you tell to the compiler that the function has no side-effect so that it could, in theory, cache its results/optimize its calls. – Matthieu M. Jul 08 '11 at 11:54
  • 1
    @Cicada: The compiler is not able to make any *semantic* change, or is it? The requirement that the compiler must fulfill is that the result of the execution must be that of the written code, if it can prove that removing the function call does not affect the program behavior, then it can remove the call. Consider inlined functions, or multiple calls to a *pure* function with the same arguments. As long as the arguments don't change executing the pure function will always yield the same value, so that the compiler can avoid calling the same function multiple times. – David Rodríguez - dribeas Jul 08 '11 at 11:56

3 Answers3

19

There is no standard way of doing so, but some compilers have annotations that you can use to that effect, for example, in GCC you can use the __attribute_pure__ tag in a function (alternatively __attribute__((pure))) to tell the compiler that the function is pure (i.e. has no side effects). That is used in the standard C library extensively, so that for example:

char * str = get_some_string();
for ( int i = 0; i < strlen( str ); ++i ) {
    str[i] = toupper(str[i]);
}

Can be optimized by the compiler into:

char * str = get_some_string();
int __length = strlen( str );
for ( int i = 0; i < __length; ++ i ) {
   str[i] = toupper(str[i]);
}

The function is declared in the string.h header as:

extern size_t strlen (__const char *__s)
     __THROW __attribute_pure__ __nonnull ((1));

Where __THROW is a no throw exception in case that it is a C++ compiler parsing the function, and __nonnull((1)) tells the compiler that the first argument should not be null (i.e. trigger a warning if the argument is null and -Wnonnull flag is used).

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    This looks promising. Anyone knows of something similar in MSVC? (Of course a standard solution would be better, but well...) – imre Jul 08 '11 at 11:56
  • @David: do you know if the compiler makes any check when hitting the function definition wrt purity ? – Matthieu M. Jul 08 '11 at 11:57
  • @imre: If you have MSVC installed, try taking a look at `strlen`, it might have a similar attribute defined, it is a good example of a function that is known in the standard to be *pure* so that it would be a good candidate for the implementors to use that attribute. – David Rodríguez - dribeas Jul 08 '11 at 11:59
  • 1
    @Matthieu M. I don't think that the compiler performs that verification. The attribute cannot be used in the function definition, only in the declaration, and that in turn means that the compiler might not be seeing that the function was declared to be pure, so it might not have the needed information to trigger a warning/error. A simple test with g++ 4.5.2 did not yield any warning in a function that had side effects but was declared pure. – David Rodríguez - dribeas Jul 08 '11 at 12:03
  • @David: to be used with caution then :/ – Matthieu M. Jul 08 '11 at 12:07
  • 1
    Just found this: http://stackoverflow.com/questions/2798188/pure-const-function-attributes-in-different-compilers – imre Jul 08 '11 at 12:10
5

The compiler cannot optimize away a call to the opaque function. However, if GetCategory is inline, and, hence visible at the call site, the compiler is allowed to, and it most cases, will optimize it away if it sees that it doesn't have side effects, but is not mandated to do so.

To achieve what you want with 100% certainty you need to wrap the entire statement in a macro that will evaluate to an empty statement for your release configuration.

Alex B
  • 82,554
  • 44
  • 203
  • 280
  • How is wrapping a statement in a macro going to help? By the time the code gets to the compiler proper, that macro doesn't exist. It has been expanded. **Edit** Never mind. The macro expands into nothing in production mode. – David Hammen Jul 08 '11 at 11:50
  • @David, I should've been more explicit. Edited. – Alex B Jul 08 '11 at 11:53
  • 1
    Yeah, I'm aware of the option of wrapping the whole thing in one macro, but I dislike it as a solution for two reasons: a) it's a bit ugly, and b) somehow I feel that even if right now I could only come up with these two examples, this is in fact a more general problem, and the ability to flag functions as having no side effects could have further uses. Optimizing only inlined functions isn't really enough; a function argument may be the result of a very complex computation (no side effects), and if it's ignored, it would still be nice to skip the entire call. – imre Jul 08 '11 at 11:55
  • @imre, well, if you put it this way, there is no (portable) language support. You either use compiler-specific language extensions or other features (like link-time optimization, where more information about functions is accessible across code modules). – Alex B Jul 08 '11 at 12:00
  • I believe, you are wrong here: If GetCategory() is declared as "pure", as recommended by the first answer, and if the result of GetCategory() is not used by the calling code, the call to GetCategory() will be optimized out by GCC! Even, if the code of GetCategory() is NOT inlined. This can be easily tested - e.g., in the following code fragment, the compiler emits a call to "g", but no call to "f": int f(int x) __attribute__((pure)); int g(void); void h(void) { f(g()); } – Kai Petzke Jan 20 '14 at 19:07
4

This is a know issue with having Debug Mode code.

The only reliable solution (for function calls) is to wrap all the debugging code within the macro itself.

For example, you could perhaps use the following code instead:

LOG("message", GetCategory());

Then the preprocessor would wipe out the whole statement in Release, and you would not have to worry about this any longer.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722