DEV Community

Discussion on: Your bash scripts are rubbish, use another language

 
xtofl profile image
xtofl • Edited

Sorry, I keep finding counterarguments to your statement, unless I misunderstood.

This article clarifies some things, too: uninformativ.de/blog/postings/2020....

Could you be talking about 'early versions' of C?

I'll try compiler explorer tomorrow - now's nap time here.

Thread Thread
 
jrbrtsn profile image
John Robertson • Edited

From the Wikipedia page:

In regard to how to return values, some compilers return simple data structures with a length of
2 registers or less in the register pair EAX:EDX, and larger structures and class objects requiring
special treatment by the exception handler (e.g., a defined constructor, destructor, or assignment)
are returned in memory. To pass "in memory", the caller allocates memory and passes a pointer to it
as a hidden first parameter; the callee populates the memory and returns the pointer, popping the
hidden pointer when returning.[2]

There's your compiler trick of silently allocating stack space, and then passing it by reference to the function to get populated. After the function returns, C++ compilers historically have then copied the result from the invisible-to-the-programmer return buffer into something the programmer knows about. For large objects this is a significant performance penalty to pay for some syntactic sugar.

Thread Thread
 
xtofl profile image
xtofl • Edited

Can you put a date on that? "Historically", that must mean pre-2003: since then, every major C++ compiler does copy elision. C++ has wildly drifted away from what C used to be, to follow its credo "don't pay for what you don't use".

But thanks. It is indeed very interesting to see how you're drawing totally opposite conclusions depending on your viewpoint. Here's a (reasonably) honest analysis of the schysm between C and C++, by someone in the standardization world: cor3ntin.github.io/posts/c/index.html

  • hardware-focused: a C compiler has to perform assembly 'tricks' to accomodate struct returning
  • problem-focused: returning structs is the most trivial thing a compiler should allow

I conclude that

  1. the C standard totally allows returning structs
  2. it leaves the semantics to compiler builders ('undefined')

Btw. with godbolt.org/z/a4exrM, you can compare the assembly generated by a large number of compilers.

Thread Thread
 
jrbrtsn profile image
John Robertson • Edited

I'll concede that my understanding of C++ compilers is dated - in 2003 I had already been coding in C++ for 10 years.
The crux of this disagreement is the definition of the word "return". In my opinion, creating the semantics of returning an object by silently passing in the reference to a possibly significantly sized return buffer of which the programmer may or may not be aware is not "returning" an object at all. Passing return buffers to a function by reference has been possible and clear since 1972, but I still cannot write in C or C++:

BigClass1, BigClass2, BigClass3 funcname(int arg1, double arg2)
{
   BigClass1 rtn1;
   BigClass2 rtn2;
   BigClass3 rtn3;
 // do stuff with arg1, arg2. Populate rtn1, rtn2, rtn3
  return rtn1, rtn2, rtn3;
}

However, I have always been able to write:

int funcname (BigClass1 *rtn1, BigClass2 *rtn2, BigClass3 *rtn3, int arg1, double arg2)
{
   // do stuff with arg1, arg2. Populate *rtn1, *rtn2, *rtn3
   return SOME_ERROR_CODE;
}

So, is the second example "returning" 3 objects? If not, then why?
By implementing the semantic charade of returning a single arbitrary object, modern compilers have accommodated a single case where it appears in source code that a function can return something which will not fit in CPU registers:

HugeSizeClass funcname (int arg1, double arg2)
{
   HugeSizeClass rtn;
   // do stuff with arg1, arg2, populate rtn
  // Throw exception on error
   return rtn;
}

So, where (stack|heap|static data segment) does rtn exist? How does the contents of rtn make it back to the caller? How is this not superfluous copying?
I contend that the following code is much clearer and guaranteed to be at least as efficient :

HugeSizeClass* funcname (HugeSizeClass *rtn, int arg1, double arg2)
{
   // do stuff with arg1, arg2, populate *rtn
   // Return NULL or throw exception on error
   return rtn;
}
Thread Thread
 
xtofl profile image
xtofl

"Returning" is indeed an abstract concept. Each platform makes it concrete in its own way. To the CPU, there's no such thing as 'returning'. I don't think opinions should matter here; it's always a choice.

To me, returning a tuple is fare more readable than mixing input- and output arguments of a function. Modern languages accomodate this, and together with 'destructuring', it leads to code that most developers can readily understand.

#python
def explode_url(url):
  ...
  return prot, dom, path

protocol, domain, path = explode_url("https://x.y.z.com/a/b/c")
Enter fullscreen mode Exit fullscreen mode
// C++
auto explode_url(const string_view& url) {
  ...
  return make_tuple(prot, dom, path);
};
auto [protocol, domain, path] = explode_url("https://x.y.z.com/a/b/c");
Enter fullscreen mode Exit fullscreen mode

As an application programmer (less so as a driver implementer or kernel hacker), I value this s expressiveness in a language. Any compiler that does not know this 'magic' forces me to work around it.

Hey, as an aside - cor3ntin wrote a nice overview of what divides C and C++ worlds: cor3ntin.github.io/posts/c/. He shouldn't have called it 'The Problem", but I like his analysis, which goes way beyond the technical.

Thread Thread
 
jrbrtsn profile image
John Robertson • Edited

I had already read the article, thanks for sharing. "Expressiveness" is an entirely subjective term which merits little discussion. In my opinion, any pointer passed in which is not marked const refers to a return buffer. If you study libc's prototypes, you can see the established convention of passing in the address of the return buffer(s) first.
Back in 1993 I would have asserted confidently that C++ will overtake C in a decade or so. Live and learn.

Thread Thread
 
xtofl profile image
xtofl

I have noticed that in most circles I have communicate with (I started around 2000), abstractions are welcomed (within limits), while in the embedded domain, the feel with the hardware is appreciated more. There seems to be a scale ranging from bare metal to functional programming.

It would be an interesting exercise to see which concepts make code more expressive for you, and which for me, and if there are certain 'clusters' of developers that benefit from the same kind of style. Probably there are some studies about it already - I'll have to look around.