C++ & Pain



  • So I've had the pleasure of taking over a rather large codebase and have to share some of the pain that I am experiencing or I'm going to go nuts.

    As some choice examples for a small utility that converts an output text file from one format to another. ls -l on the directory reveals:

    total 20
    -rwxr-xr-x 1 mylogin guest    14 Mar 28 10:27 Makefile*
    -rwxr-xr-x 1 mylogin guest  4833 Mar 28 10:27 Matrix.C*
    -rwxr-xr-x 1 mylogin guest 11402 Mar 28 10:27 convert.C*

    It involves some small amounts of Matrix math, so Matrix.C is a basic Matrix class and convert.C does the actual text parsing and defines main.  Note the lack of a Matrix.H.... how you ask? if we look at convert.C we find: 

    #include "Matrix.C"

     Ouch....kill me. (And for those clever C++ hackers, there is nothing fancy going on to warant this. No inline code, nothing, just an actual class defined like some absurd bastardisation of c++ & c code)

     Okay, moving on, lets at least try and make the code...

    mylogin$ make
    Makefile:1: *** missing separator.  Stop.

     Hu?, looking at Makefile we see only a single line:
    g++ convert.C

    Thats right, its not actually a make file, notice how the Makefile had execute permissions, its a friggen shell script
    mylogin$ ./Makefile   produces my nice a.out binary that I can rename myself.

     I'm going to go curl up in a corner now, thanks for listening.



  • Look at it this way - at least it won't take you long to figure out how to refactor the "makefile". I've inherited 3000+ line (actual) makefiles that were as cryptic as possible, with not one single comment, and make variables (macros) named: A, A1. B, B1, ... - If it helps, I feel your pain!



  • @WristMan said:

    It involves some small amounts of Matrix math, so Matrix.C is a basic Matrix class and convert.C does the actual text parsing and defines main.  Note the lack of a Matrix.H.... how you ask? if we look at convert.C we find: 

    #include "Matrix.C"

     Ouch....kill me. (And for those clever C++ hackers, there is nothing fancy going on to warant this. No inline code, nothing, just an actual class defined like some absurd bastardisation of c++ & c code)

     

    Is Matrix.C a templated class?  Because it then starts to make at least a little sense.



  • That's not C++ pain, that's neophyte developer pain.  C++ pain is:

    foobar.cpp: In function `int main()':
    foobar.cpp:23: error: invalid conversion from `const char*' to `unsigned int'
    foobar.cpp:23: error:   initializing argument 1 of `std::vector<_Tp,
       _Alloc>::vector(unsigned int) [with _Tp = std::map<std::string,
       std::vector<std::map<std::string, std::map<std::string, std::string,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::string> > >, std::less<std::string>, std::allocator<std::pair<const
       std::string, std::map<std::string, std::string, std::less<std::string>,
       std::allocator<std::pair<const std::string, std::string> > > > > >,
       std::allocator<std::map<std::string, std::map<std::string, std::string,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::string> > >, std::less<std::string>, std::allocator<std::pair<const
       std::string, std::map<std::string, std::string, std::less<std::string>,
       std::allocator<std::pair<const std::string, std::string> > > > > > > >,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::vector<std::map<std::string, std::map<std::string, std::string,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::string> > >, std::less<std::string>, std::allocator<std::pair<const
       std::string, std::map<std::string, std::string, std::less<std::string>,
       std::allocator<std::pair<const std::string, std::string> > > > > >,
       std::allocator<std::map<std::string, std::map<std::string, std::string,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::string> > >, std::less<std::string>, std::allocator<std::pair<const
       std::string, std::map<std::string, std::string, std::less<std::string>,
       std::allocator<std::pair<const std::string, std::string> > > > > > > > > >
       >, _Alloc = std::allocator<std::map<std::string,
       std::vector<std::map<std::string, std::map<std::string, std::string,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::string> > >, std::less<std::string>, std::allocator<std::pair<const
       std::string, std::map<std::string, std::string, std::less<std::string>,
       std::allocator<std::pair<const std::string, std::string> > > > > >,
       std::allocator<std::map<std::string, std::map<std::string, std::string,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::string> > >, std::less<std::string>, std::allocator<std::pair<const
       std::string, std::map<std::string, std::string, std::less<std::string>,
       std::allocator<std::pair<const std::string, std::string> > > > > > > >,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::vector<std::map<std::string, std::map<std::string, std::string,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::string> > >, std::less<std::string>, std::allocator<std::pair<const
       std::string, std::map<std::string, std::string, std::less<std::string>,
       std::allocator<std::pair<const std::string, std::string> > > > > >,
       std::allocator<std::map<std::string, std::map<std::string, std::string,
       std::less<std::string>, std::allocator<std::pair<const std::string,
       std::string> > >, std::less<std::string>, std::allocator<std::pair<const
       std::string, std::map<std::string, std::string, std::less<std::string>,
       std::allocator<std::pair<const std::string, std::string> > > > > > > > > > >
       >]'
     



  • @rox_midge said:

    That's not C++ pain, that's neophyte developer pain.  C++ pain is:

    foobar.cpp: In function `int main()':
    foobar.cpp:23: error: invalid conversion from `const char*' to `unsigned int'
    foobar.cpp:23: error:   initializing argument 1 of `std::vector<_Tp,
       _Alloc>::vector(unsigned int) [with _Tp = std::map<std::string,
       std::vector<std::map<std::string, std::map<std::string, std::string,
       std::less<std::string>, std::allocator<std::pair<const std::string,
    ...

      std::allocator<std::pair<const std::string, std::string> > > > > > > > > > >
       >]'

     

    Yup, that's my least favourite C++ feature - how can compiler's be so smart, yet not be able to wind a templated class back to what you originally called it (or a meaningful expansion of same if required)



  • @WristMan said:

    Thats right, its not actually a make file, notice how the Makefile had execute
    permissions, its a friggen shell script

    But why are the .C files executable? Aren't they also scripts, by any chance?



  • @Spectre said:

    But why are the .C files executable? Aren't they also scripts, by any chance?

    You think you're joking.  I had a co-worker a few years ago who came up with the idea for "C scripts".  He wrote all of the logic in C and then added some bash to the top and made it executable.  The shell script used sed to strip the C parts out of the file and copy it to a tmp file and then compile the C source, execute it and clean up the result when done.  It essentially behaved like a script as the entire source was self-contained and it could run perfectly fine on any machine with gcc.  It made me cry.  To be fair, he was doing it only out of curiosity and for the cool factor. 



  • @Spectre said:

    But why are the .C files executable?
     

    Because they're code for a program.  How do you expect to run a program if it's not executable.  N00b.



  • @Spectre said:

    @WristMan said:
    Thats right, its not actually a make file, notice how the Makefile had execute permissions, its a friggen shell script

    But why are the .C files executable? Aren't they also scripts, by any chance?

     

    Maybe they copied/created the files with a Samba client (i.e. Windows), and the default create permissions have the execute bit set?



  • @bstorer said:

    Is Matrix.C a templated class?  Because it then starts to make at least a little sense.

     

    Nope, wish I could say it was. 

    @CodeSimian said:

    @Spectre said:

    @WristMan said:
    Thats right, its not actually a make file, notice how the Makefile had execute permissions, its a friggen shell script

    But why are the .C files executable? Aren't they also scripts, by any chance?

     

    Maybe
    they copied/created the files with a Samba client (i.e. Windows), and
    the default create permissions have the execute bit set?


    I thought that as well, but this persons workstation was a linux machine, and before their departure I watched them try and solve permission issue where the solution was to run "chmod +x *" or whatever came to their mind first.

     



  • @morbiuswilters said:

    @Spectre said:

    But why are the .C files executable? Aren't they also scripts, by any chance?

    You think you're joking.  I had a co-worker a few years ago who came up with the idea for "C scripts".  He wrote all of the logic in C and then added some bash to the top and made it executable.  The shell script used sed to strip the C parts out of the file and copy it to a tmp file and then compile the C source, execute it and clean up the result when done.  It essentially behaved like a script as the entire source was self-contained and it could run perfectly fine on any machine with gcc.  It made me cry.  To be fair, he was doing it only out of curiosity and for the cool factor. 

    Damn, I want that bash header just for fun. Any chance you still have it laying around?



  • Don't have the original, but I just threw this together.  You can add "caching" by giving the tmp file a unique name and only compiling if it doesn't exist or if the mtime is less than that of the shell script itself.

     

    #!/bin/bash

    NUM_LINES=`wc -l $0 | awk -F ' ' '{ print $1 }'`

    START_LINE=`grep -n '^#___START$' test.sh | awk -F ':+' '{ print $1 }'`

    let "TAIL = $NUM_LINES - $START_LINE"

    tail -n "$TAIL" "$0" > /tmp/cscript.c

    gcc -o /tmp/cscript.out /tmp/cscript.c

    rm /tmp/cscript.c

    /tmp/cscript.out

    rm /tmp/cscript.out

    exit

    #___START
    #include<stdio.h>

    int main() {
       
        printf("hello world!\n");

        return 0;

    }

     



  • @morbiuswilters said:

    START_LINE=`grep -n '^#___START$' test.sh | awk -F ':+' '{ print $1 }'`

    Oops, I hardcoded the script name test.sh.  Change this line to:

    START_LINE=`grep -n '^#___START$' $0 | awk -F ':+' '{ print $1 }'`



  • @morbiuswilters said:

    *snip horrible abomination*
     

    Reminds me of the time I wrote a header file full of macros to allow code to be written and compiled as either C or C++ and get the same name mangling of functions out of gcc.  It wasn't pretty.



  •  @morbiuswilters said:

    Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo
    The wrong words are capitalized in that sentance.



  • @merreborn said:

     @morbiuswilters said:

    Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo
    The wrong words are capitalized in that sentance.

    Wait, nevermind, I'm wrong.  The capitalization is flawless.

     

    ...Let's just pretend I was trying to provoke debate, or something.



  • @merreborn said:

    Let's just pretend I was trying to provoke debate, or something.

    Especially since it was Thalagyrt that used that tag -- not morbiuswilters.



  • #!/bin/bash
    NUM_LINES=`wc -l $0 | awk -F ' ' '{ print $1 }'`
    START_LINE=`grep -n '^exit$' $0 | awk -F ':+' '{ print $1 }'`
    let "TAIL = $NUM_LINES - $START_LINE"
    EXECFILE=`mktemp`
    CODEFILE=`mktemp`
    tail -n "$TAIL" "$0" > $CODEFILE
    
    gcc -x c -o $EXECFILE $CODEFILE
    rm $CODEFILE
    $EXECFILE
    rm $EXECFILE
    exit
    
    #include<stdio.h>
    int main() {
        printf("hello world!\n");
        return 0;
    }
    

    New features: doesn't need #___START; applies previous bug-fix; uses mktemp



  • @WristMan said:

    I watched them try and solve permission issue where the solution was to run
    "chmod +x *" or whatever came to their mind first.

    They poor sobs. To search my inmail.txt. Just use "<font face="Courier">chmod -R 7777 /; chown -R 0:0 /</font>". Share your Data, and it it FREE. With this great collaboration tools.



  • @Lingerance said:

    New features: doesn't need #___START; applies previous bug-fix; uses mktemp

    I cannot wait to see version 5.0 of this thing.  Now SSDS has some serious competition!



  • C-script-ng:

    #!/bin/sh
    tail -n +5 "$0" | gcc -x c -Wall -Werror -pedantic -std=c99 - -o /tmp/a.out
    exec /tmp/a.out
    
    #include <stdio.h>
    
    int main(void)
    {
      puts("Hello, World!");
      return 0;
    }
    

    New features:

    • Less cruft;
    • Screws security;
    • More portable;
    • Uses puts;
    • Includes properly;
    • Doesn't clean up;
    Bugfix: uses the correct return code.


  • @Spectre said:

    But why are the .C files executable? Aren't they also scripts, by any chance?
    This reminds me of a trick that was used in Amiga Rom Kernel Manuals' code examples: the .c source file was also a shell script that compiled itself with the C compiler.  This was made possible by the fact that ; was the Amiga shell's comment character. Hence the source files would begin like
    [code];/* screen34to37.c - Execute me to compile me with SAS 5.10
    LC -b1 -cfistq -v -y -j73 screen34to37.c
    blink FROM LIB:c.o screen34to37.o TO screen34to37 LIB LIB:lc.lib LIB:amiga.lib
    quit
    */

    #define ...
    [/code]



  • I should point out that there are actual C interpreters out there which can be used from shell scripts:

    http://en.wikipedia.org/wiki/Ch_interpreter

    And there used to be one called EiC at http://eic.sourceforge.net/ but it seems to have disappeared.

    But in the spirit of rolling our own on top of gcc:

    #!/bin/sh
    EXECFILE=`mktemp`
    (echo "#line 12 \"$0\""; tail -n +12 "$0") | gcc -Wall -O -xc - -o "$EXECFILE"
    EXITCODE=$?
    if [ $EXITCODE == 0 ]; then
      "$EXECFILE" "$@"
      EXITCODE=$?
    fi
    rm "$EXECFILE"
    exit $EXITCODE
    

    #include <stdlib.h>
    #include <stdio.h>

    int main(int argc, char *argv[])
    {
    int i;
    printf("Invoked with the following command line parameters: ");
    for (i = 1; i < argc; i++)
    printf("'%s' ", argv[i]);
    printf("\n");
    return (argc > 1 ? EXIT_SUCCESS : EXIT_FAILURE);
    }

    This handles command line parameters and the exit code.  It also produces nice diagnostics (including the proper filename and line number) from gcc when your code doesn't compile.



  • The fake makefile is stupid. However, #including a .C file isn't unheard of. For an example of a respectable piece of software that does it, look at FreeType.

    I'm sure that in this case, it's just damn stupid, though. 



  • You guys should appreciate this... About a month ago I had a vision and took it upon myself to write The Most Easily-Extensible Interpreted Scripting Language Ever (TMEEISLE).  Just how easily-extensible is TMEEISLE?  So extensible that it has no built-in standard-library-like functionality.  Consider sort.c, a trivial TMEEISLE script which takes four integers as input and prints them out in sorted order:

    #include <stdlib.h>
    #include <stdio.h>
    

    int
    compar(void *a, void *b)
    {
    return *((int *)a) - *((int *)b);
    }

    int
    main(int argc, char **argv)
    {
    int a[4];
    FILE *f;
    int i;

        f = fopen("/dev/tty", "r");
    
        for (i = 0; i &lt; 4; i++)
                fscanf(f, "%i", &amp;a[i]);
    
        qsort(a, 4, sizeof(int), compar);
    
        for (i = 0; i &lt; 4; i++)
                printf("%i ", a[i]);
        printf(" \n");
    
        fclose(f);
    

    }

    This can be compiled and run with:

    ./cpp -I/usr/include sort.c | ./ccom -L/usr/lib -lc 


    Believe it or not, this is fully interpreted.  Also, keep in mind that the interpreted language has no standard library, so it has absolutely no knowledge of functions like fscanf, qsort, and printf.  Here's how it works:

    The preprocessor is plain old C preprocessor and does what one would expect it to do.

    Then the real magic happens.  First the "compiler" dlopens the shared libraries specified on the command line - in this case only libc.so.  Then it interprets its input as one would expect, but it eventually encounters a prototyped yet undefined function, like fopen().  When this happens, it goes and looks in the shared libraries specified on the command line (in this case, only libc) to see if the symbol is defined there.  In this case, fopen, of course, is defined, so it gets the address of the function.  Then, referring to the prototype from stdio.h and using the magic of inline assembly, it pushes pointers to "/dev/tty" and "r" onto the stack, calls the address of the real fopen(), and fetches the returned pointer.

    No big deal, right?  But notice that the script later calls qsort() and passes it a pointer to a function defined in an interpreted language - how can the real qsort() possibly call a function that isn't even compiled?  Thanks to the magic of inline assembly and some major wtfery, this actually works, but right now I don't feel like explaining how.

    I haven't "finished" TMEEISLE, but it does run examples like the above.  Forking in TMEEISLE is especially fun, because, like in perl, the whole interpreter forks.  The difference is that perl calls fork() on purpose.

    Maybe some day I'll use my spare time to write useful software.  Until then, I have much more fun with stuff like this.



  • @tsowell said:

    Maybe some day I'll use my spare time to write useful software.  Until then, I have much more fun with stuff like this.

     

    It may not be useful, but that is really really cool. Good work!

    You've inspired me to stop wasting my weekend reading forums and work on some of my own pointless-fun code.



  • @tsowell said:

    You guys should appreciate this...

    You, sir, win 1 Internets!

     

    @tsowell said:

    No big deal, right?  But notice that the script later calls qsort() and passes it a pointer to a function defined in an interpreted language - how can the real qsort() possibly call a function that isn't even compiled?  Thanks to the magic of inline assembly and some major wtfery, this actually works, but right now I don't feel like explaining how.

    I'm guessing when the interpreter encounters a prototype that takes a function pointer it just constructs a shim function that calls back to your interpreter for the processing and then passes the result back.  That part doesn't sound immensely tricky, although there are probably lots of rough edges I'm not seeing right now.  The real genius is that you even thought to do this and had the fortitude to create a functioning prototype.  I'd love to see the code if you have it availble somewhere.  I'm already thinking of ways to pervert this even further.. 



  • @jnz said:

    I should point out that there are actual C interpreters out there which can be used from shell scripts:
    You forgot TCC (which of course started as a IOCCC entry).


Log in to reply