Re: Type punning question
On Jun 17, 9:12 pm, Travis Vitek <travis.vi...@gmail.com> wrote:
I'm maintaining some code that uses the SunOS priocntl system
call [*], and this has led me to some questions about type
punning. To avoid a long-winded explaination, I'm just going
to post some source and then ask my questions.
#define PC_CLNMSZ 16
#define PC_CLINFOSZ (32 / sizeof (int))
// The pc_clinfo member is used to return data describing the
// attributes of a specific class. The format of this data is
// class-specific and is described under the appropriate
// heading.
typedef struct pcinfo
{
int pc_cid; /* class id */
char pc_clname[PC_CLNMSZ]; /* class name */
int pc_clinfo[PC_CLINFOSZ]; /* class information */
} pcinfo_t;
// realtime class attributes in the pc_clinfo buffer are in
// this format.
This is a typical, but somewhat problematic, use for Posix
functions to return varying types of information. A cleaner
solution, here, would be for the system to specify a union for
pc_clinfo. An array of char or unsigned char would also be
acceptable, provided they ensured that the preceding
declarations were such that no padding would ever be inserted
according to the rules of the system API.
typedef struct rtinfo
{
short rt_maxpri;
} rtinfo_t;
int main ()
{
pcinfo_t pc;
// assume a call to priocntl() will initialize pc with
// something like this...
// pc.pc_clname [0] = 'R';
// pc.pc_clname [1] = 'T';
// pc.pc_clname [2] = 0;
//if (-1L == priocntl (idtype_t(), id_t(), PC_GETCLINFO, &pc))
// return -1;
//
rtinfo_t* prt = (rtinfo_t*)pc.pc_clinfo;
return 0 == prt->rt_maxpri;
}
The gcc-4.1.1 compiler generates the following warning when
casting pc.pc_clinfo (an array of int) to rtinfo_t*.
t.cpp: In function 'int main()':
t.cpp:38: warning: dereferencing type-punned pointer will break
strict-aliasing rules
It is clear that we are type-punning the pointer, but I do not
see how this particular case is dangerous.
Three considerations concerning "type-punning" in general:
-- As far as the standard goes, any access through ptr is
undefined behavior, since all sorts of considerations
(alignment restrictions, possible trapping representations,
etc.) apply, and it is impossible for the standard to really
delineate all of the possibilities.
-- There is an obvious intent that implementations should make
this do something reasonable for the targetted platform.
Otherwise, there would be no point in having
reinterpret_cast at all.
-- Compiler implementers need a maximum of restrictions on
possible aliasing; the fact that pointers to different types
cannot alias the same memory is important to them.
From a QoI point of view, the fact that the cast is visible (and
that there really is a possibility of intentional aliasing)
means that the second consideration should predominate, and
there should be no problem.
That's in general. In this particular case, the issue is even
clearer. You never access pc.pc_clinfo as an int[], so there's
no aliasing involved. I don't think your code contains any
undefined behavior at all; in fact, I think that accessing
pc.pc_clinfo without the case would result in undefined
behavior. The system clearly "constructed" an rtinfo_t object
in the memory defined (reserved) by pc.pc_clinfo. In this case,
it would be using pc.pc_clinfo (as an int[] or an int*) without
first casting it into a rtinfo_t* which would result in
undefined behavior, see =A73.8/4 ("A program may end the lifetime
of any object by reusing the storage which the object
occupies"---i.e. what the system does with pc.pc_clinfo in
priocntl) and =A73.10/15. ("If a program attempts to acces the
stored value of an object through an lvalue of other than one of
the following types the behavior is undefined [...]"; the system
has reused the memory to construct an rtinfo_t here, so only
access through an lvalue with the possibly cv_qualified type
rtinfo_t, char or unsigned char is allowed.)
While some of these questions may be more appropriate on the
gcc list, I'm hoping to better understand how this is supposed
to work, not how it happens to work on one implementation.
The standard itself is very vague about this.
My questions are as follows...
1. is this actually a bogus warning?
In this case, definitely. It's bogus unless there are actual
accesses through both types concerned (int[] and rtinfo_t). It
would be valid from a standards point of view (but not from a
QoI point of view) if you accessed through both types, and it
would be valid, generally, if you let one or both pointers
"escape", by passing them to an external function. (E.g. it
might be valid if you did the cast before calling priocntl,
supposing that g++ doesn't know what priocntl does.)
It's a reinterpret_cast, and traditionally, reinterpret_cast
(and casts in general) have been the way to tell the compiler
that you know something it can't know. And to shut up warnings.
From a QoI point of view, warning that a reinterpret_cast might
behave like a reinterpret_cast seems a poor decision to me.
2. is this a dangerous thing to do?
In a few cases.
It's a reinterpret_cast, and the usual restrictions concerning
reinterpret_cast apply: don't use it in code you consider
"portable", and don't use it unless you really know what you're
doing. In your code, there's not the slightest problem.
2. why does changing pc_clinfo to array of char eliminate the
warning? i.e., does that make the cast 'safe'?
Again, it's not so much the cast which is unsafe, but what you
might do with the resulting pointer. In the case of char* or
unsigned char*, the standard says that accesses are allowed
through the resulting pointers, which means that the compiler
must consider that a char* and a rtinfo_t* might point to the
same memory, and avoid any reordering that would cause a program
to fail if they did. In the case of int* and rtinfo_t*, the
standard says that if they are in fact aliases, and you access
through both, your code has undefined behavior, so the compiler
can assume no aliasing.
3. what techniques are there for avoiding problems like this?
Use only cleanly designed and cleanly written code, and the
problem won't occur. Of course, this means that you can forget
about using the Windows or the Posix API (or any other system
API I've seen), and just about all third party libraries. Which
is fine as long as you don't need anything not part of the
standard: no system level IO, no communications with other
processes or machines, no GUI, no threading...
In this case, I'd encapsulate the direct interface to the system
in a very low level component (not just because of the warning),
and document that this component does generate this particular
warning, and that it can safely be ignored.
If my understanding of the standard is correct, the above does
indeed invoke undefined behavior, but I'm not certain how
exactly to best work around the issue and to avoid dangerous
punning in the future.
See above. There's nothing dangerous in what you're doing. The
only thing that worries me is that such spurious warnings
strongly suggest that the people (or at least some of the
people) working on g++ don't understand low (system) level
programming.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34