[Courses] C Programming For Absolute Beginners, Lesson 5A: All About Functions, Part 2

Christopher Howard christopher.howard at frigidcode.com
Tue Apr 3 21:42:49 UTC 2012


On 04/03/2012 07:11 AM, Carla Schroder wrote:
> I don't see how this makes a case against creating unique function names. 
> Sure, you can do it.  Regardless of any circumstances where duplicate function 
> names would not cause a program to run incorrectly, why would you want the 
> confusion?
>

Strictly speaking, I wasn't trying to make a case against creating
unique function names. I was only responding to your statement that
function names "must" be unique throughout an entire program, which as I
demonstrated is not true.

@Peck: Personally, the ability to create static functions is important
to me because it gives me the ability to name a bunch of local functions
whatever the heck I want without having to wonder each single time
whether or not I have used or ever will use that function name in any
other source file in my project, or in any past or present or future
library I will ever use. I like convenience and freedom -- so sue me. I
mean, really... how can you really be certain that every function name
you every write is and always will be totally unique, unless you always
use really long and cumbersome names like
"my_program_name_network_module_local_3_bit_nand()". Just use "static
3_bit_nand()" and then you can change it later in that one source code
file in the unlikely event that you ever need to.

If, as you say, I need to rename the function so I can export it, or so
I can import a similarly named function, this is not hard. The function
only needs to be renamed in that source file, which in Emacs can be done
with a single search-and-replace command, which takes about two seconds
to type.

> 
> 
>> --------
>>
>>> C functions can be written five different ways:
>>>
>>> 1--No arguments or return values
>>> 2--Arguments and no return values
>>> 3--Arguments and return values
>>> 4--No arguments and return values
>>> 5--Return multiple values
>>
>> Item 5 should be excluded from the list. Speaking technically, all
>> functions in C return one value and one value only. This might be a
>> struct containing other data objects, yet even in such a case it is only
>> one value that is returned by the function (the memory address of the
>> struct). I suppose one could argue semantic nuances here, but even on
>> the highest level we recognize that a "return" statements returns only
>> one evaluated result.
> 
> Nah, it stays on the list :). Whatever the semantic nuances, which are good to 
> understand clearly, it's still operating with multiple returned values.
> 

Well, you're the teacher... But the C99 states "The return type
[singular] of a functions shall be void or an object type [singular]
other than array type." (sect. 6.9.1) So long as every one understands
that a C function can only return one object (or void) and that it
cannot return an array of objects (only a single reference to one) that
is probably good enough.


> 
> Fish fiddle dee dee, functions are not dangerous!  Functions are your program. 
> Modern compilers perform all manner of optimizations, including deciding which 
> functions to inline. Manually inlining a function is compiler-dependent, and 
> useful in limited circumstances, which we will cover in this course. 
> 
> Would you explain your inline object code example? What performance difference 
> does this show? 
> 

Sorry, my previous example was not very good as a performance
demonstration. This is a little better:

code:
--------
$ cat howardonimize.c
#include <stdio.h>

inline int howardonimize (int x)
{
  return x*x + 3*x + 12;
}

int main ()
{
  int a;

  // Get all data from standard input.
  // Could be a huge file of space separated numbers
  while(1)
    {
      int ret = scanf("%d", &a);

      // out of data
      if(ret == EOF)
	{
	  break;
	}

      // to get rid of any "junk" in the buffer
      if(ret != 1)
	{
	  getchar();
	  continue;
	}

      // sends space separated output numbers to
      // standard out
      printf ("%d ", howardonimize(a));

      // in real life, you probably want overflow check also
    }

  // nice end of line at end of file, to be compliant
  // with HOWARDX data file standards
  puts("");
}
$ gcc -O2 howardonimize.c -o howardonimize
$ cat testinputdata | ./howardonimize > testoutputdata
$ cat testoutputdata
1515973170 270953758 55470 -265027752 1416 21982042 13968916 1198100708
-319032502 468550 991030 -1900284126 1902579614 971220 9910 96737070
1492 969250 5710 87413160 120 30 82 -1347342946 1172069470 706450 2460
-1722971754 894638020 7482970
$ objdump -d howardonimize
...snip...
00000000004006d0 <howardonimize>:
  4006d0:	8d 47 03             	lea    0x3(%rdi),%eax
  4006d3:	0f af c7             	imul   %edi,%eax
  4006d6:	83 c0 0c             	add    $0xc,%eax
  4006d9:	c3                   	retq
  4006da:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)

00000000004006e0 <main>:
  4006e0:	53                   	push   %rbx
  4006e1:	48 83 ec 10          	sub    $0x10,%rsp
  4006e5:	48 8d 5c 24 0c       	lea    0xc(%rsp),%rbx
  4006ea:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
  4006f0:	31 c0                	xor    %eax,%eax
  4006f2:	48 89 de             	mov    %rbx,%rsi
  4006f5:	bf 4c 08 40 00       	mov    $0x40084c,%edi
  4006fa:	e8 b1 fe ff ff       	callq  4005b0 <__isoc99_scanf at plt>
  4006ff:	83 f8 ff             	cmp    $0xffffffffffffffff,%eax
  400702:	74 3c                	je     400740 <main+0x60>
  400704:	83 f8 01             	cmp    $0x1,%eax
  400707:	74 17                	je     400720 <main+0x40>
  400709:	48 8b 3d 28 09 20 00 	mov    0x200928(%rip),%rdi        #
601038 <__bss_start>
  400710:	e8 8b fe ff ff       	callq  4005a0 <_IO_getc at plt>
  400715:	eb d9                	jmp    4006f0 <main+0x10>
  400717:	66 0f 1f 84 00 00 00 	nopw   0x0(%rax,%rax,1)
  40071e:	00 00
  400720:	8b 44 24 0c          	mov    0xc(%rsp),%eax
  400724:	be 4f 08 40 00       	mov    $0x40084f,%esi
  400729:	bf 01 00 00 00       	mov    $0x1,%edi
  40072e:	8d 50 03             	lea    0x3(%rax),%edx
  400731:	0f af d0             	imul   %eax,%edx
  400734:	31 c0                	xor    %eax,%eax
  400736:	83 c2 0c             	add    $0xc,%edx
  400739:	e8 42 fe ff ff       	callq  400580 <__printf_chk at plt>
  40073e:	eb b0                	jmp    4006f0 <main+0x10>
  400740:	bf 52 08 40 00       	mov    $0x400852,%edi
  400745:	e8 26 fe ff ff       	callq  400570 <puts at plt>
  40074a:	48 83 c4 10          	add    $0x10,%rsp
  40074e:	5b                   	pop    %rbx
  40074f:	c3                   	retq
...snip...
--------

So, as you can see, howardonimize() itself is never called, but it's
internal code is inlined into the main() function, at address 40072e, I
believe. Because of that, the code didn't have to go to the trouble of
making a jump to the function's memory address, or arranging the
function's parameters, or preserving registers across the function call.

Now, a good compiler might inline the function anyway, even if you don't
use the "inline" keyword. I'm not trying to push the usage of the
"inline" keyword, but only to demonstrate my original point: all else
being equal, at the lowest level code with a function call will actually
be slightly less efficient (in terms of raw cycles needed) than the
equivalent code without one. With a small data set, that difference will
be negligible, but if you had three trillion numbers to crunch, it might
make a measurable difference.

(There may be complex caching issues at work here as well... I don't
dare broach that subject.)

-- 
frigidcode.com
indicium.us



More information about the Courses mailing list