0

The following code tries to rename an existing file to a new location. In my environment, the rename fails because source and destination are on different mount points.

Given the failure, why is the value of errno reported as 0 in version2()? I can't imagine errno not being set synchronously with rename(), and I imagined that operands to std::ostream::operator<< were evaluated in left-to-right order. If that were true, shouldn't errno have acquired its nonzero value from rename's failure before it was passed to the output operator?

Obviously reality is not matching what I imagined; can someone explain the order of evaluation or other relevant reasons why version2 outputs an errno value of 0?

The code:

// main.cpp
// Prereq: "./" and "/tmp" are on different mount points.
// Prereq: "./foo.txt" exists.

#include <cstdio>
#include <iostream>
#include <cerrno>
#include <cstring>

void version1()
{
  std::cout << __FUNCTION__ << std::endl;
  std::cout << rename( "./foo.txt", "/tmp/bar.txt" ) << std::endl;
  std::cout << errno << ": " << strerror( errno ) << std::endl;
}

void version2()
{
  std::cout << __FUNCTION__ << std::endl;
  std::cout << rename( "./foo.txt", "/tmp/bar.txt" ) << ": "
            << errno << ": " << strerror( errno ) << std::endl;
}

int main( int argc, char* argv[] )
{
  errno = 0;
  version1();
  errno = 0;
  version2();
  return 0;
}

Compilation & output:

> g++ --version && g++ -g main.cpp && ./a.out 
g++ (GCC) 4.8.3 20140911 (Red Hat 4.8.3-7)
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

version1
-1
18: Invalid cross-device link
version2
-1: 0: Success

(I searched SO for existing questions addressing this, but either none exist, or my search phrasings weren't adequate matches for anything existing)

Update:
I think this question resolves down to "what is the order of evaluation of the operands to the output operator?" in which case, according to Order of evaluation of arguments using std::cout the answer is "unspecified." In the spirit of academic learning (and generating information useful to the community), I'm curious if anyone is aware of the history why the order of evaluation of output stream operands is undefined: It seems intuitive that they should be evaluated left-to-right...?

StoneThrow
  • 5,314
  • 4
  • 44
  • 86
  • 1
    What happens if you call `version2` before `version1` (swap them)? – NathanOliver Oct 08 '18 at 21:06
  • could they be evaluated in a non left to right order? – Grady Player Oct 08 '18 at 21:08
  • @NathanOliver - result: the order of output is swapped, but content is the same (version2 still shows the unexpected value `0`) – StoneThrow Oct 08 '18 at 21:08
  • @GradyPlayer - possibly, but it'd be nice to know for certain if this was well-defined behavior somewhere. – StoneThrow Oct 08 '18 at 21:09
  • Am I missing something here - the first function renames the file so the next rename attempt fails? –  Oct 08 '18 at 21:10
  • @NeilButterworth - not quite: the first function fails to rename the file because source and destination directories are on different mount points. I'm tempted to say this is besides the point - but I'm not sure if that's true. The question is more like: given two seemingly-identical attempts to rename a file, which should both fail identically, why is `errno` unexpectedly `0` in one case? – StoneThrow Oct 08 '18 at 21:13
  • 2
    No, it seems from right to left, try this code: ` int a = 10; cout << a++ << " " << a++ << " " << a++ ;`, you will find that 10 is the last one to be printed – user9335240 Oct 08 '18 at 21:13
  • @user9335240 - whoa: that's trippy - what's going on??? Is this well-known/defined behavior that output stream operands are evaluated right-to-left??? This is new to me. – StoneThrow Oct 08 '18 at 21:16
  • 2
    What happens if you capture the value of `errno` before doing any other operations that may alter the value of `errno`? – Eljay Oct 08 '18 at 21:25
  • https://stackoverflow.com/questions/14809978/order-of-execution-in-operator – pm100 Oct 08 '18 at 21:28
  • @StoneThrow "unspecified" until C++17, which adds more rules to how things are sequenced and evaluated. In the case of `operator<<`, the right-hand operand is not evaluated until after the left-hand operand, and that also includes when `operator<<` is chained, thus the effects of one `<<` call are evaluated before subsequent `<<` calls. See [Order of Evaluation](https://en.cppreference.com/w/cpp/language/eval_order). So, in the example given, `errno` should be stable *unless* one of the `<< ": "` calls overwrites it. – Remy Lebeau Oct 08 '18 at 21:37
  • 1
    Standard trap, there is extra code between the expression that failed and the expression that evaluates errno. It is `<< ": "`. iostream is unfit to get a proper job done in multiple ways, first thing you throw out. – Hans Passant Oct 08 '18 at 22:17
  • 1
    @StoneThrow: If you draw the abstract syntax tree, you'll quickly see that there is no technical reason for the operands to be evaluated -- it's not possible to traverse the directed graph from any operand to any other operand. The only ordering that exists is completely artificial, introduced in C++17 to help un-confuse programmers who think of their code in terms of sequences of tokens, not trees. – Ben Voigt Oct 08 '18 at 22:31
  • @BenVoigt - that's a little over my head: I don't (yet) know about ASTs. What is a simplification of your observation? Is it that because the AST does not _require_ an order of evaluation that none is imposed? (other than the noted artificial order introduced in C++17) – StoneThrow Oct 08 '18 at 22:51
  • 1
    @StoneThrow: Actually I guess it's the dependency graph, not AST, that is important here. But yes, because the operands don't depend on each other, the compiler can arrange for their evaluation in any order. That sort of flexibility can really help with CPU scheduling, since different operations have different latency, a particular order might evaluate more quickly while another order stalls the pipeline several times. – Ben Voigt Oct 08 '18 at 23:42
  • @HansPassant - good observation, but not what's at fault here I think. Simple experiment: (1) perform something that sets `errno` to nonzero, (2) stream `errno` to `std::cout` and observe that it is nonzero, (3) stream "hello world" to `std::cout`, (4) stream `errno` to `std::cout` and observe it is still nonzero, i.e. a successful stream to `std::cout` does not set `errno` to zero. I believe this experiment effectively simulates the "extra code" you pointed out. I think the root cause here is the undefined order of evaluation, as others have highlighted. – StoneThrow Oct 09 '18 at 16:54

0 Answers0