2

I have the following code (works only on gcc):

#include <iostream>
#include <cstdlib>
#include <string>
#include <typeinfo>
#include <cxxabi.h>

const std::string demangle (const char* name) {

    int status = -4;
    char* res = abi::__cxa_demangle(name, 0, 0, &status);
    const char* const demangled_name = (status == 0) ? res : name;
    std::string ret_val(demangled_name);
    std::free(res);

    return ret_val;
}

template <typename T>
const std::string getname (T x)
{
    return demangle(typeid(x).name());
}

int main()
{
   std::add_const<int>::type *p = static_cast<const int *>(0); 
   std::cout << getname(*p) << std::endl;
}

On my local computer (with gcc 4.7.0(experimental) it crashes (run with gdb gives a segfault). However, with ideone.com, it prints "int" as expected. Heres, a link to the example. Also, getting rid of the template and calling demangle(typeid(x).name()) directly fixes the problem, so what is wrong with the template?

EDIT I forgot to include the type_traits header (doh!) which fixed the problem, however I would still like to know what is going on a little better.

Jesse Good
  • 50,901
  • 14
  • 124
  • 166
  • 4
    Well, the obvious answer is that you are dereferencing a null pointer, which is UB, and so both results that you observe are "correct". – Kerrek SB Jan 19 '12 at 04:44
  • 1
    @todda.speot.is: As specified by the standard, it's undefined behavior in C++. If you want to talk about specific C++ implementations on specific platforms on specific hardware at a specific time that's fine, but realize you are no longer talking about the C++ *language*. – GManNickG Jan 19 '12 at 04:58
  • @GMan Admittedly there is undefined behaviour for some operations involving NULL pointers in the C++ standard, so I take my comments back. However http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232 seems to consider it defined behaviour in certain scenarios, and after reading around a bit more it seems that in this case it's defined behaviour [Objects of class `std::bad_typeid` are thrown when `typeid` is called on a dereferenced null pointer](http://en.wikipedia.org/wiki/Typeid). Although it's undefined for other operations. – ta.speot.is Jan 19 '12 at 05:13
  • 2
    @todda.speot.is: Note that issue is still active, and AFAIK was not been incorporated into the new standard. ([This was an area of interest to me](http://stackoverflow.com/questions/2474018/when-does-invoking-a-member-function-on-a-null-instance-result-in-undefined-beha/2474021#2474021).) Anyway, the sentence in that article is actually too lose, the relevant section in the standard is §5.2.8/2. It says very specifically that the expression *in the `typeid` expression* can take the form `*p`, `*(p)`, etc., and `p` can be null. That isn't the case here, where UB has already occurred. – GManNickG Jan 19 '12 at 05:29
  • @GMan You are correct, `*p` is not the expression given to `typeid`. – ta.speot.is Jan 19 '12 at 05:41
  • @GMan: Thanks, you last comment cleared everything up for me, if it was an answer, I would have set it as the answer. – Jesse Good Jan 19 '12 at 05:45
  • @todda.speot.is: (Note that Ive subsequently edited the article to clarify.) – GManNickG Jan 19 '12 at 05:45
  • @Jesse: No problem, I'll just let the comments be comments (if the moderators allow...) and you can give Nawaz the points, I've enough rep. :) – GManNickG Jan 19 '12 at 05:47
  • @GMan: **§5.2.8/2** only applies to glvalue expressions whose types are polymorphic class types. **§5.2.8/3** regulates the non-polymorphic case but is silent on the issue of null pointers, however it says *unevaluated operand* which suggests to me it's fine, but I could not find a citation backing up this claim. – Matthieu M. Jan 19 '12 at 07:43
  • @MatthieuM.: Oops, right you are. Since it doesn't evaluate it, the value of the expression doesn't matter, only the type. – GManNickG Jan 19 '12 at 07:50
  • @GMan: that is my guess too, but I didn't find anything close to it. – Matthieu M. Jan 19 '12 at 07:57
  • @MatthieuM.: It's defined (rather loosely) in §5/8. §4.1/2 also touches on it. – GManNickG Jan 19 '12 at 08:09

2 Answers2

3
std::add_const<int>::type *p = static_cast<const int *>(0); 

p is a null pointer, and dereferencing it (i.e *p) invokes give undefined behaviour (UB). You are lucky that it gives segfault. And since it is actually UB, all compilers (and all versions) may not give segfault, for that is what UB means, i.e anything could happen.

Why don't you try this instead:

std::cout << getname(int()) << std::endl;
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 2
    @todda.speot.is: Did you even read the linked article? "While this works correctly in many compilers, it has undefined behavior according to the C standard, since it involves both a dereference of a null pointer, and a cast that violates the aliasing rules. It also tends to produce confusing compiler diagnostics if one of the arguments is misspelled. Modern compilers often define the macro using a special form instead." It's undefined behavior. Stop trying to define it. – GManNickG Jan 19 '12 at 05:00
  • @todda.speot.is: There is no such thing as "userspace" in C++. Dereferencing a null pointer *in C++* is undefined behaviour, though that is not at variance with having a particular platform provide additional guarantees. – Kerrek SB Jan 19 '12 at 05:01
  • @GMan I had a hunt around the C99 standard and while I found something *close* to defining that implementation of `addressof` as defined behaviour it's explict enough. So you're right in the regard, I should have read the article. – ta.speot.is Jan 19 '12 at 05:22
  • @GMan : What cast is violating aliasing rules? – ildjarn Jan 24 '12 at 20:03
  • @ildjarn: todda.speot.is had linked to [this Wikipedia page](http://en.wikipedia.org/wiki/Offsetof), I was quoting it. – GManNickG Jan 24 '12 at 20:35
  • @GMan : Ah, I see; I thought you were referring to something in the OP's question or this answer and was wondering what I was missing. :-] – ildjarn Jan 24 '12 at 21:45
3

There is a subtle issue here: it's a problem of evaluation.

Normally, dereferencing a null pointer is Undefined Behavior; however there is a corner case (at least on gcc), that invoking typeid(*p) where p is null is a valid expression.

Therefore the issue here resides in your level of indirection:

int* p = 0;

getname(*p);       // p is null, *p invokes undefined behavior

typeid(*p).name(); // p is null, but it's probably okay in `typeid`
                   // because it's an unevaluated operand here

For those interesting in delving deeper, the typeid expression is described in detail in §5.2.8 (C++11).

2/ When typeid is applied to a glvalue expression whose type is a polymorphic class type (10.3), the result refers to a std::type_info object representing the type of the most derived object (1.8) (that is, the dynamic type) to which the glvalue refers. If the glvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10), the typeid expression throws the std::bad_typeid exception (18.7.3).

Okay, so typeid(*p) is defined if p points to a polymorphic class... and in this case it throws.

3/ When typeid is applied to an expression other than a glvalue of a polymorphic class type, the result refers to a std::type_info object representing the static type of the expression. Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) conversions are not applied to the expression. If the type of the expression is a class type, the class shall be completely-defined. The expression is an unevaluated operand (Clause 5).

Interestingly, no mention of null pointers for non-polymorphic classes. I suspect the unevaluated operand is what allows this, but have yet to find the citation.

6/ If the header <typeinfo> (18.7.1) is not included prior to a use of typeid, the program is ill-formed.

No diagnostic seems required. At least on gcc I think it fires off a warning though.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722