Tuesday Feb 08, 2005

C++, name mangling and function signatures

One of the guys in the office was having problems compiling POVray on solaris - the last link step kept failing with an unknown symbol. It turns out that the POVray source had declared the symbol in the .h file using the pattern:
int function(const char \*, unsigned int);
but defined the function in the .cpp file using the pattern:
int function(const char \*, const unsigned int) {}
When it came to the link step, the compiler complained that other object files referencing the function could not link, because the function as declared was not in any of the linked object files. We can easily ascertain this fact using the nm utility. To more easily see the differences, you need to use the -C option, which performs the neat-oh C++ name demangling.
The example code is here. Which demonstrates this issue precisely.
First we compile, but do not link the .cpp files. To perform this step do: CC -c \*.cpp.
% nm -C \*.o


inter.o:

[Index]   Value      Size    Type  Bind  Other Shndx   Name

[2]     |         0|      25|FUNC |GLOB |0    |2      |int function(const char\*,const unsigned)
                                                       [__1cIfunction6FpkckI_i_]
[1]     |         0|       0|FILE |LOCL |0    |ABS    |inter.cpp


link.o:

[Index]   Value      Size    Type  Bind  Other Shndx   Name

[4]     |         0|       0|NOTY |GLOB |0    |ABS    |__fsr_init_value
[2]     |         0|       0|FUNC |GLOB |0    |UNDEF  |int function(const char\*,unsigned)
                                                       [__1cIfunction6FpkcI_i_]
[1]     |         0|       0|FILE |LOCL |0    |ABS    |link.cpp
[3]     |         0|      41|FUNC |GLOB |0    |2      |main
without the demangling, you would see the function names in brackets, which are not easy to distinguish, but once you add in the demangling the difference is obvious. Of course even if you #included the .h file into the .cpp file to 'type enforce' the function signature, because to the compiler they are not the same function it simply presumes that the declaration in the .h file is for another function.
It is annoying, but you have to pay attention to these things when you're writing C++.
Contrast the patterns observed when we compile using the gnu compiler: g++ -c \*.cpp
% nm -C \*.o


inter.o:

[Index]   Value      Size    Type  Bind  Other Shndx   Name

[2]     |         0|       0|SECT |LOCL |0    |1      |
[3]     |         0|       0|SECT |LOCL |0    |2      |
[5]     |         0|       0|SECT |LOCL |0    |4      |
[4]     |         0|       0|SECT |LOCL |0    |3      |
[6]     |         0|      10|FUNC |GLOB |0    |1      |function(const char\*, unsigned)
                                                       [_Z8functionPKcj]
[1]     |         0|       0|FILE |LOCL |0    |ABS    |inter.cpp


link.o:

[Index]   Value      Size    Type  Bind  Other Shndx   Name

[5]     |         0|       0|SECT |LOCL |0    |5      |
[2]     |         0|       0|SECT |LOCL |0    |1      |
[3]     |         0|       0|SECT |LOCL |0    |3      |
[4]     |         0|       0|SECT |LOCL |0    |4      |
[6]     |         0|       0|SECT |LOCL |0    |7      |
[9]     |         0|       0|NOTY |GLOB |0    |UNDEF  |__gxx_personality_v0
[8]     |         0|       0|NOTY |GLOB |0    |UNDEF  |function(const char\*, unsigned)
                                                       [_Z8functionPKcj]
[1]     |         0|       0|FILE |LOCL |0    |ABS    |link.cpp
[7]     |         0|      53|FUNC |GLOB |0    |1      |main
Who is correct at this step? I don't quite have the answer for that, as if you tried to define two functions that differed by that const, the compiler has no way to disambiguate them. If you're being really picky, you could say that dropping the const against standard pass-by-value variables is incorrect; after all it's a function taking a const, and it's declaration should respect that.

Monday Feb 07, 2005

The curse of make (svr)

Sounds like it should be a movie title. The curse of the black-hearted and arcane makefiles.
On one of our projects we're constantly adding subdirectories of tests that need to be picked up by the harness. Somebody started to place the names of the subdirectories in a variable in the Makefile, so it looked like:
SUBDIRS=acl mount umount
This is all very fine and well until we start having 50+ directories, and a policy which states that the subdirectories have to be kept in order and that you can't exceed 80 characters per line (queue lots and lots of backslashes and rearranging of lines every time you add a new subdirectory). This is sub-optimal, so I changed the SUBDIRS variable like so:
SUBDIRS :sh =  /bin/ls \*/[Mm]akefile | sed -e '@/[Mm]akefile@@g'
The :sh tells make that the SUBDIRS variable is set by the output from the ls command filtered through the sed command. Short and simple.
Welcome to the world of development. Sometimes we need to EXCLUDE certain subdirectories - the functionality isn't there (it happens), the command line options change (ui reviews cause this a lot).
Fixing this is a bit more difficult. The first try is normally:
This does not work!!!!
ALL_SUBDIRS :sh =  /bin/ls \*/[Mm]akefile | sed -e '@/[Mm]akefile@@g'
EXCLUDE_SUBDIRS = cli

SUBDIRS :sh = \\
        for all in $(ALL_SUBDIRS); do \\
                for excl in $(EXCLUDE_SUBDIRS); do \\
                        if [ "$excl" = "$all" ]; then \\
                                all=""; \\
                        fi; \\
                done; \\
                echo $all; \\
        done
The reason why this does not work is that at the first pass, the variables ALL_SUBIRS and EXCLUDE_SUBDIRS are not set (it's a make thing), so what we need to do is add a level of indirection to the solution.
ALL_SUBDIRS :sh =  /bin/ls \*/[Mm]akefile | sed -e '@/[Mm]akefile@@g'
EXCLUDE_SUBDIRS = cli

FOO = \\
        for all in $(ALL_SUBDIRS); do \\
                for excl in $(EXCLUDE_SUBDIRS); do \\
                        if [ "$excl" = "$all" ]; then \\
                                all=""; \\
                        fi; \\
                done; \\
                echo $all; \\
        done

SUBDIRS = $(FOO:sh)
This works, but it contains a subtle bug! If you don't have any entries in EXCLUDE_SUBDIRS, then the expansion fails with a syntax error: sh: syntax error at line 1: `;' unexpected . This error is caused as the code: for var in <>; do requires a token, so we have to change it once more:
ALL_SUBDIRS :sh =  /bin/ls \*/[Mm]akefile | sed -e '@/[Mm]akefile@@g'
EXCLUDE_SUBDIRS = cli

FOO = \\
        for all in $(ALL_SUBDIRS); do \\
                for excl in $(EXCLUDE_SUBDIRS) WILLNEVERMATCH; do \\
                        if [ "$excl" = "$all" ]; then \\
                                all=""; \\
                        fi; \\
                done; \\
                echo $all; \\
        done

SUBDIRS = $(FOO:sh)
but this means that we need to go through the loop even if EXCLUDE_SUBDIRS is never set, so more changes:
ALL_SUBDIRS :sh =  /bin/ls \*/[Mm]akefile | sed -e '@/[Mm]akefile@@g'
EXCLUDE_SUBDIRS = cli

FOO = \\
        if [ -n "$EXCLUDE_SUBDIRS" ]; then \\
                for all in $(ALL_SUBDIRS); do \\
                        for excl in $(EXCLUDE_SUBDIRS) WILLNEVERMATCH; do \\
                                if [ "$excl" = "$all" ]; then \\
                                        all=""; \\
                                fi; \\
                        done; \\
                        echo $all; \\
                done; \\
        else\\
                echo $(ALL_SUBDIRS); \\
        fi

SUBDIRS = $(FOO:sh)
This has two lessons associated with it.
  • Make is not easy
  • remember to test all the variants of a solution
About

petesh

Search

Categories
Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today