tags:

views:

116

answers:

2

I'm updating some old code that has several POD structs that were getting zero'd out by memset (don't blame me...I didn't write this part). Part of the update changed some of them to classes that use private internal pointers that are now getting wiped out by the memset.

So I added a [non-virtual] reset() method to the various structs and refactored the code to call that instead.

One particular struct developed an "undefined reference to `blah::reset()'" error.

Changing it from a struct to a class fixed the error.

Calling nm on the .o file h, the mangled function names for that method look the same (whether it's a class or a struct).

I'm using g++ 4.4.1, on Ubuntu.

I hate the thought that this might be a compiler/linker bug, but I'm not sure what else it could be. Am I missing some fundamental difference between structs and classes? I thought the only meaningful ones were the public/private defaults and the way everyone thinks about them.

Update: It actually depends on the way the it's declared:

typedef struct
{
  ...
  void reset();
} foo;

won't link.

struct foo
{
  ...
  void reset();
};

links fine.

So, maybe just a lack of understanding on my part about the way typedefs work in this context?

+1  A: 

Of course, the second declaration is the "proper" C++ way of doing things. Still, this links for me with g++ 4.4.1:

typedef struct {
    void f() {}
} S;


int main() {
    S s;
    s.f();
}

I must confess that the whole struct namespace thing has always been one of the darker corners of C for me, but I'm sure someone else will come up with an explanation.

Edit: OK, minimalist code that reproduces the problem:

// str.h
typedef struct {
    void f();
} S;

// str.cpp
#include "str.h"     
void S :: f() {
}

// sm.cpp
#include "str.h"    
int main() {
    S s;
    s.f();
}

Compiled with:

g++ sm.cpp str.cpp

Error:

undefined reference to S::f()

Now if someone can give us chapter and verse from the standard on why this doesn't work - obviously a struct namespace issue, I would have thought.

anon
Just to note that your `f()` is implicitly inline so you shouldn't have cross-TU linking problems as you have to (and do) provide a definition in every TU that use it.
Charles Bailey
@Charles Good point - I'll produce a more realistic example.
anon
The code compiles and links with clang++
Johannes Schaub - litb
@Johannes So now the only question is "who is right?" Actually, I would have thought this must have cropped up many times when rewriting C code to C++.
anon
@Neil i've just asked the clang folks on IRC. let's see what they say :)
Johannes Schaub - litb
+2  A: 

I think that your problem (and I don't have a standards quote to back this up) is that because your struct doesn't have a name, your member function also does not have a globally identifiable name.

Although you're allowed to use a typedef name to introduce a member function definition, that member function must be part of a named type if you are going to be able to link it to a definition in a different TU.

typedef struct S_ { void reset(); } S;

void S::reset() // OK, but the function actually has id: S_::reset()
{
    // ...
}

typedef struct { void reset(); } T;

void T::reset() // OK, defintion of anonymous struct's reset(),
                // but this isn't an id that can cross TUs.
{
    // ...
}

Edit: This could be a gcc bug, though.

7.1.3 [dcl.typedef] If the typedef declaration defines and unnamed class (...), the first typedef-name declared by the declaration to be that class type (...) is used to denote the class type (...) for linkage purposes only (3.5).

Edit:

Or gcc might be right. While the class has external linkage via its typedef name (3.5/4), a member function has external linkage only if the name of the class has external linkage. Although the class has external linkage and it has a name for linkage purposes only it is still an unnamed class, so it's member functions have no linkage.

Charles Bailey
+1 I couldn't find the standards reference either, but I think you're right here.
Mark B
However if you do `typedef struct { void reset(); } S;` in one TU and `struct S { void reset(); };` in another TU you violate the one definition rule and cause undefined behavior. So it could be that GCC mangles these cases differently since the ODR allows it to do so.
Johannes Schaub - litb
@litb: I've changed my mind! I now think that the first `reset()` has no linkage...
Charles Bailey
@litb: Also, gcc definitely considers member functions of anonymous structs (even with a typedef) to have either internal linkage or no linkage as a test compile with no link generated no function defintion at all even for a non-inline version of `reset`.
Charles Bailey
I think the Standard is a bit unclear. Your interpretation makes sense, i think. But another interpretation is that "name for linkage purposes" means anywhere were linkage is concerned, the class is considered to have a name.
Johannes Schaub - litb
In this test-case, gcc gives the member function external linkage: http://codepad.org/PlZuPxc0
Johannes Schaub - litb
Yes, that was my "gcc defence-lawyer" reading. It definitely says if the _name of the class_ rather than if the _class_ has external linkage; I don't know exactly if this is meant to mean what I manage to read from it. The name for linkage purposes would definitely be still used to distinguish the appearance of the type in function signatures. e.g. `void f(S*);` needs to be resolvable across TUs.
Charles Bailey
@Charles, i asked the clang folks, and they pointed me to `3.5/4` (right before the one ruling the linkage of member functions), which says "A name having namespace scope has external linkage ... if it is the name of ... an unnamed class defined in a typedef ...". So this says that the name of an unnamed class defined in such a way is the name-for-linkage-purposes-only.
Johannes Schaub - litb
The "name of an unnamed class". My head's going to explode. So are you saying that the _name of the class_ in (3.5/5) has to be the name-for-linkage-purposes-only because we're trying to determine its linkage; and it has external linkage due to (3.5/4) then so must the member function. I find this hard to refute.
Charles Bailey
@Charles yeah that's what i think is going on.
Johannes Schaub - litb
@Johannes @Charles Have you guys come to some conclusion on this? Or is the answer "the standard is vague in this area"?
anon
@Neil Butterworth: It might not be 100% clear, but I'm definitely leaning towards "member functions of an unnamed class which has a linkage-name (and hence external linkage) via a typedef, should themselves have external linkage".
Charles Bailey