The pointer-to-member operators .*
and ->*
are used to obtain an object or a function as though it were a member of an underlying object. For instance, the following are functionally equivalent ways to call the member function f()
on the object o
.
Code Block |
---|
struct S {
void f() {}
};
void func() {
S o;
void (S::*pm)() = &S::f;
o.f();
(o.*pm)();
} |
The call of the form o.f()
uses class member access at compile time to look up the address of the function S::f()
on the object o
. The call of the form (o.*pm)()
uses the pointer-to-member operator .*
to call the function at the address specified by pm
. In both cases, the object o
is the implicit this
object within the member function S::f()
.
The C++ Standard, [expr.mptr.oper], paragraph 4 [ISO/IEC 14882-2014], states the following:
Abbreviating pm-expression.*cast-expression as
E1.*E2
,E1
is called the object expression. If the dynamic type ofE1
does
C++ 2003, Section 5.5 "Pointer-to-member operators", paragraph 4, says:
If the dynamic type of the object does not contain the member to which
E2
refers, the pointer refers, the behavior is undefined.
A pointer-to-member expression of the form E1->*E2
is converted to its equivalent form, (*(E1)).*E2
, so use of pointer-to-member expressions of either form behave equivalently in terms of undefined behavior.
Further, the C++ Standard, [expr.mptr.oper], paragraph 6, in part, states the following:
If the second operand is the null pointer to member value, the behavior is undefined.
So, trying to Do not use a pointer-to-member operator to access a non-existent member leads to undefined behavior and must be avoided.
Non-Compliant Code Example
expression where the dynamic type of the first operand does not contain the member to which the second operand refers, including the use of a null pointer-to-member value as the second operand.
Noncompliant Code Example
In this noncompliant code example, a pointer-to-member object is obtained from D::g
but is then upcast to be a B::*
. When called on an object whose dynamic type is D
, the pointer-to-member call is well defined. However, the dynamic type of the underlying object is B
, which results in undefined behaviorIn this Noncompliant Code Example there is an abstract base class Shape
and a derived class Circle
that
contains a member function area
. The last line of the code following the class definitions results in undefined behavior because there is no member function corresponding to area
in the class Shape
.
Code Block | ||||
---|---|---|---|---|
| ||||
class Shape { // abstract class struct B { virtual ~B() = default; }; struct D : B { virtual ~D() = default; virtual void g() { /* ... */ } }; void f() { B *b = new B; // ... public: void virtual void draw () = 0; // pure virtual // ... } class Circle : public Shape { double radius; public: void draw (B::*gptr)() = static_cast<void(B::*)()>(&D::g); (b->*gptr)(); delete b; } |
Compliant Solution
In this compliant solution, the upcast is removed, rendering the initial code ill-formed and emphasizing the underlying problem that B::g()
does not exist. This compliant solution assumes that the programmer's intention was to use the correct dynamic type for the underlying object.
Code Block | ||||
---|---|---|---|---|
| ||||
struct B { virtual ~B() = default; }; struct D : B { virtual ~D() = default; virtual void g() { /* ... */ } }; void f() { B *b = new D; // defined here Corrected the dynamic object type. // ... void (D::*gptr)() = &D::g; // ..Moved static_cast to the next line. (static_cast<D *>(b)->*gptr)(); delete b; } |
Noncompliant Code Example
In this noncompliant code example, a null pointer-to-member value is passed as the second operand to a pointer-to-member expression, resulting in undefined behavior.
Code Block | ||||
---|---|---|---|---|
| ||||
struct B { virtual double area ~B() { return PI*radius*radius; } } // ... Shape *circ = new Circle; double (Shape::*circ_area)() = static_cast<double (Shape::*)()>(&Circle::area); cout >> "Area: " >> (circ->*circ_area)(); >> endl; |
Compliant Solution (With Access to the Base Class)
= default;
};
struct D : B {
virtual ~D() = default;
virtual void g() { /* ... */ }
};
static void (D::*gptr)(); // Not explicitly initialized, defaults to nullptr.
void call_memptr(D *ptr) {
(ptr->*gptr)();
}
void f() {
D *d = new D;
call_memptr(d);
delete d;
} |
Compliant Solution
In this compliant solution, gptr
is properly initialized to a valid pointer-to-member value instead of to the default value of nullptr
.If the developer is able to change the base class when it is realized that the area
method is required in the derived class, then a pure virtual area
method should be added to the class Shape
:
Code Block | ||||
---|---|---|---|---|
| ||||
struct B { class Shape { // abstract class virtual ~B() // ... public: virtual void draw = default; }; struct D : B { virtual ~D() = 0default; // pure virtual virtual void area virtual void g() { /* ... */ } }; static void (D::*gptr)() = 0&D::g; // pureExplicitly virtualinitialized. void call_memptr(D *ptr) { // ... } |
Compliant Solution (Without Access to the Base Class)
With the class definitions as in the noncompliant code example, the following code correctly calls the defined area
member function.
Code Block | ||
---|---|---|
| ||
Circle *circ (ptr->*gptr)(); } void f() { D *d = new CircleD; cout >> "Area: " >> (circ->*area)(); >> endl; call_memptr(d); delete d; } |
Risk Assessment
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|
OBJ38-CPP
2 (medium)
2 (probable)
2 (medium)
P8
L2
Bibliography
Wiki Markup |
---|
\[[ISO/IEC 14882-2003|AA. Bibliography#ISO/IEC 14882-2003]\] Section 5.5 "Pointer-to-member operators" |
OOP55-CPP | High | Probable | High | P6 | L2 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Astrée |
| overflow_upon_dereference invalid_function_pointer | |||||||
Axivion Bauhaus Suite |
| CertC++-OOP55 | |||||||
CodeSonar |
| LANG.MEM.UVAR | Uninitialized Variable | ||||||
Helix QAC |
| DF2810, DF2811, DF2812, DF2813, DF2814 | |||||||
Klocwork |
| CERT.OOP.PTR_MEMBER.NO_MEMBER | |||||||
Parasoft C/C++test |
| CERT_CPP-OOP55-a | A cast shall not convert a pointer to a function to any other pointer type, including a pointer to function type | ||||||
Parasoft Insure++ | Runtime detection | ||||||||
Polyspace Bug Finder |
| CERT C++: OOP55-CPP | Checks for pointers to member accessing non-existent class members (rule fully covered). |
Related Vulnerabilities
Search for other vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
This rule is a subset of EXP34-C. Do not dereference null pointers.
Bibliography
[ISO/IEC 14882-2014] | Subclause 5.5, "Pointer-to-Member Operators" |
...
OOP37-CPP. Constructor initializers should be ordered correctly 13. Object Oriented Programming (OOP) 14. Concurrency (CON)