Topic: modifying a pointer, compiler complains
Author: "Dan Baker" <dbaker@saffire.com>
Date: 2000/11/29 Raw View
I have a case where I'm reading in data from disk, filling a structure. The
data from the disk contains pointers, but the pointers are relative to zero.
All the pointers need to be "fixed up" (the address of the structure needs
to be added to every pointer contained in the structure). Here is a
snippet of code:
CFromDisk* pData; // pointer to the data read from disk
(long)pData->mPointerOne += (long)pData;
(long)pData->mPointerTwo += (long)pData;
I have done this before in C without problems. But, the C++ compiler
complains:
"error C2106: '+=' : left operand must be l-value"
I have changed my code as follows:
CFromDisk* pData; // ptr to the data read from disk
*(long*)&pData->mPointerOne += (long)pData;
*(long*)&pData->mPointerTwo += (long)pData;
Which works just fine. But, I don't understand why the first way (the old C
way) doesn't work.
Comments?
Dan
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: Pete Becker <petebecker@acm.org>
Date: 2000/11/29 Raw View
Dan Baker wrote:
>
> I have a case where I'm reading in data from disk, filling a structure. The
> data from the disk contains pointers, but the pointers are relative to zero.
> All the pointers need to be "fixed up" (the address of the structure needs
> to be added to every pointer contained in the structure). Here is a
> snippet of code:
>
> CFromDisk* pData; // pointer to the data read from disk
> (long)pData->mPointerOne += (long)pData;
> (long)pData->mPointerTwo += (long)pData;
>
> I have done this before in C without problems. But, the C++ compiler
> complains:
> "error C2106: '+=' : left operand must be l-value"
>
> I have changed my code as follows:
> CFromDisk* pData; // ptr to the data read from disk
> *(long*)&pData->mPointerOne += (long)pData;
> *(long*)&pData->mPointerTwo += (long)pData;
>
> Which works just fine. But, I don't understand why the first way (the old C
> way) doesn't work.
>
It didn't actually work in C, either: the result of a cast is not an
l-value. But you got fooled by Microsoft's compiler, which allows this
sort of hackery. In any event, you're deep into non-portability, so
whatever works is what works, and the standard has nothing to say about
it.
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Contributing Editor, C/C++ Users Journal (http://www.cuj.com)
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: James Kuyper <kuyper@wizard.net>
Date: Thu, 30 Nov 2000 01:02:08 GMT Raw View
Dan Baker wrote:
>
> I have a case where I'm reading in data from disk, filling a structure. The
> data from the disk contains pointers, but the pointers are relative to zero.
"pointers relative to zero" is a non-portable concept. You shouldn't be
storing them in pointer variables, you should be storing them in
ptrdiff_t variables.
> All the pointers need to be "fixed up" (the address of the structure needs
> to be added to every pointer contained in the structure). Here is a
> snippet of code:
>
> CFromDisk* pData; // pointer to the data read from disk
> (long)pData->mPointerOne += (long)pData;
> (long)pData->mPointerTwo += (long)pData;
replace this with:
ptrdiff_t mOffsetOne = (char *)p1 - (char *)p2;
/*or whatever method you use to calculate the offset */
void *mPointerOne = (char *)pData + mOffsetOne;
/* or */
T *mPointerTwo = (T*)((char *)pData+mOffsetTwo);
> I have done this before in C without problems. But, the C++ compiler
If p is a pointer, and i is an integer, even assuming that it's safe to
convert pointers to longs (which is NOT guarateed by the standard),
(void*)((long)p+i) isn't guaranteed to compare equal to (void*)((char
*)p+i).
It's non-portable even in C, for exactly the same reasons. If it worked
for your compiler, you were "lucky", in a way. Actually, you were
unlucky, because you were misled into believing that it's actually a
safe thing to do.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: Barry Margolin <barmar@genuity.net>
Date: 2000/11/30 Raw View
In article <1GdV5.1309$Ta5.156640@news.uswest.net>,
Dan Baker <dbaker@saffire.com> wrote:
>I have a case where I'm reading in data from disk, filling a structure. The
>data from the disk contains pointers, but the pointers are relative to zero.
>All the pointers need to be "fixed up" (the address of the structure needs
>to be added to every pointer contained in the structure). Here is a
>snippet of code:
>
>CFromDisk* pData; // pointer to the data read from disk
>(long)pData->mPointerOne += (long)pData;
>(long)pData->mPointerTwo += (long)pData;
>
>I have done this before in C without problems. But, the C++ compiler
>complains:
>"error C2106: '+=' : left operand must be l-value"
>
>I have changed my code as follows:
>CFromDisk* pData; // ptr to the data read from disk
>*(long*)&pData->mPointerOne += (long)pData;
>*(long*)&pData->mPointerTwo += (long)pData;
>
>Which works just fine. But, I don't understand why the first way (the old C
>way) doesn't work.
C has never allowed casts to be the target of an assignment, although some
C compilers (e.g. GCC) have allowed it as an extension (interpreting it
like the expressions that you rewrote it as). C++'s default conversion
operators are similar, although you can probably define conversion
operators for user-defined types that allow it.
--
Barry Margolin, barmar@genuity.net
Genuity, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: James.Kanze@dresdner-bank.com
Date: 2000/11/30 Raw View
In article <1GdV5.1309$Ta5.156640@news.uswest.net>,
"Dan Baker" <dbaker@saffire.com> wrote:
> I have a case where I'm reading in data from disk, filling a
> structure. The data from the disk contains pointers, but the
> pointers are relative to zero.
So actually, what you have aren't pointers, but offsets in the disk
file (or some such). And have been stored as some integral type.
> All the pointers need to be "fixed up" (the address of the structure
> needs to be added to every pointer contained in the structure).
> Here is a snippet of code:
> CFromDisk* pData; // pointer to the data read from disk
> (long)pData->mPointerOne += (long)pData;
> (long)pData->mPointerTwo += (long)pData;
> I have done this before in C without problems.
No you haven't. You may have done this in a language similar to C,
which claimed to be C, but this has always been illegal in C (although
many prestandard compilers allowed it). The result of a type
conversion is not an lvalue, and the left side of all of the
assignment operators must be an lvalue.
> But, the C++ compiler
> complains:
> "error C2106: '+=' : left operand must be l-value"
As should any C compiler.
> I have changed my code as follows:
> CFromDisk* pData; // ptr to the data read from disk
> *(long*)&pData->mPointerOne += (long)pData;
> *(long*)&pData->mPointerTwo += (long)pData;
> Which works just fine.
Because you're lucky, and sizeof(T*)==sizeof(long), and there are no
funny representations involved.
The basic problem is that sizeof(T*)==sizeof(long) is not guaranteed.
This means that, supposing the offsets written to disk are longs, you
cannot simply use them as pointers. The actual pointer value you want
is reinterpret_cast< T* >( reinterpret_cast< char* >( pData ) + offset
).
When writing the data, you have to do the reverse: something like:
template< typename T >
void
write( streambuf& dst , T* ptr )
{
write( dst , reinterpret_cast< char* >( ptr ) - base ) ;
}
> But, I don't understand why the first way (the old C way) doesn't
> work.
Because it never has worked. Because it cannot be made to work
portably. (Think about it for a minute. I've worked on machines with
48 bit pointers, and with 16 bit pointers. Both with 32 bit longs.
What should the semantics of what you claim works be in these cases?)
--
James Kanze mailto:kanze@gabi-soft.de
Conseils en informatique orient e objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelh ttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
Sent via Deja.com http://www.deja.com/
Before you buy.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "Dan Baker" <dbaker@saffire.com>
Date: 2000/11/30 Raw View
"James Kuyper" <kuyper@wizard.net> wrote in message
news:3A25A633.936118D8@wizard.net...
> Dan Baker wrote:
> >
> > I have a case where I'm reading in data from disk, filling a structure.
The
> > data from the disk contains pointers, but the pointers are relative to
zero.
>
> "pointers relative to zero" is a non-portable concept. You shouldn't be
> storing them in pointer variables, you should be storing them in
> ptrdiff_t variables.
>
> > All the pointers need to be "fixed up" (the address of the structure
needs
> > to be added to every pointer contained in the structure). Here is a
> > snippet of code:
> >
> > CFromDisk* pData; // pointer to the data read from disk
> > (long)pData->mPointerOne += (long)pData;
> > (long)pData->mPointerTwo += (long)pData;
>
> replace this with:
>
> ptrdiff_t mOffsetOne = (char *)p1 - (char *)p2;
> /*or whatever method you use to calculate the offset */
>
> void *mPointerOne = (char *)pData + mOffsetOne;
> /* or */
> T *mPointerTwo = (T*)((char *)pData+mOffsetTwo);
I think I understand what you are saying here, and I agree with it. But, I
think my case is a little different (or just plain strange).
The basic idea of what I'm trying to do is as follows:
I have a tool which generates data files for my program
to use. The data files contain structures and 'raw data' (like ASCII
strings). The structures contain pointers to other structures contained
within the data file, as well as pointers to 'raw data' contained within the
data file. BUT, the "pointers" in the data file are simply offsets from the
beginning of the data file. Obviously, the tool doesn't know the physical
address the data file will be loaded to at run time, so the tool simply
stores offsets to the other structures and data (it stores these offsets
*in* the pointers in the structures). This is the *wierd* part. I'm using
the pointers in the structures for two different things. I could go to the
effort of making a union at every pointer (union the pointer with a
ptrdiff_t); access the "offset" via the ptrdiff_t; add this "offset value"
to the know pointer value; and store this new fixed up pointer value back
into the "pointer" of the union. It just seems like going to all that work
makes the code less readable, and harder to maintain.
So, then at run time: The program mallocs a chunk of memory the size of the
file, reads in the file, and then it needs to perform "fix ups" on all
pointers in all structures. These fix-ups are a very simple operation:
simply add the address of the malloc to every pointer in every structure
read in from the disk data file, and voila, the pointers contained in the
structures read from disk are now correct.
> > I have done this before in C without problems.
>
> If p is a pointer, and i is an integer, even assuming that it's safe to
> convert pointers to longs (which is NOT guarateed by the standard),
> (void*)((long)p+i) isn't guaranteed to compare equal to (void*)((char
> *)p+i).
> It's non-portable even in C, for exactly the same reasons. If it worked
> for your compiler, you were "lucky", in a way. Actually, you were
> unlucky, because you were misled into believing that it's actually a
> safe thing to do.
Good point, and I agree that the cast to (long) is strange.
But, the same problem happens when casting it to a (char*):
CFromDisk* pData; // pointer to the data read from disk
(char*)pData->mPointerOne += (long)pData;
(char*)pData->mPointerTwo += (long)pData;
I know it is ok to add a long or int to a pointer. The problem is the cast
magically makes the pointer no longer a valid l-value.
My question is, why does casting a perfectly good pointer make the pointer
no longer a valid l-value? Is there some way in C++ to do this, like
reinterpret_cast?
Dan
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: jpotter@falcon.lhup.edu (John Potter)
Date: Fri, 1 Dec 2000 00:12:59 GMT Raw View
On Thu, 30 Nov 2000 18:43:27 GMT, "Dan Baker" <dbaker@saffire.com>
wrote:
No lecture here. You are hacking, lets get the syntax right.
> > > CFromDisk* pData; // pointer to the data read from disk
> > > (long)pData->mPointerOne += (long)pData;
> > > (long)pData->mPointerTwo += (long)pData;
You need an lvalue, use one:
(long&)pData->mPointerOne += (long)pData;
A reference is an lvalue, a value is not.
> Is there some way in C++ to do this, like reinterpret_cast?
Those are reinterpret casts in C++. Better to spell it out.
reinterpret_cast<long&>(pData->mPointerOne) +=
reinterpret_cast<long>(pData);
Much uglier, that's good.
John
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: James Kuyper <kuyper@wizard.net>
Date: 2000/12/01 Raw View
Dan Baker wrote:
...
> The basic idea of what I'm trying to do is as follows:
> I have a tool which generates data files for my program
> to use. The data files contain structures and 'raw data' (like ASCII
> strings). The structures contain pointers to other structures contained
> within the data file, as well as pointers to 'raw data' contained within the
> data file. BUT, the "pointers" in the data file are simply offsets from the
> beginning of the data file. Obviously, the tool doesn't know the physical
Then they aren't pointers, they're offsets, and should be stored as some
appropriate integer type, not in a pointer type. It should be 'long' if
you're using ftell()/fseek(), fpos_t if you're using
fgetpos()/fsetpos(), and traits::pos_type if you're using
std::basic_ios<charT,traits>.
If you're using any of the standard typdefs like std::iostream, then by
default you're indirectly using
std::basic_ios<char,std::char_traits<char> >, and pos_type is therefore
std::streampos.
> address the data file will be loaded to at run time, so the tool simply
> stores offsets to the other structures and data (it stores these offsets
> *in* the pointers in the structures). This is the *wierd* part. I'm using
> the pointers in the structures for two different things. I could go to the
> effort of making a union at every pointer (union the pointer with a
> ptrdiff_t); access the "offset" via the ptrdiff_t; add this "offset value"
> to the know pointer value; and store this new fixed up pointer value back
> into the "pointer" of the union. It just seems like going to all that work
> makes the code less readable, and harder to maintain.
I wouldn't recommend using a union, though that's a better approach than
what you're currently doing. What you should be doing is keep the
pointers and the offsets well separated. The design you currently use
runs the risk that you might try to dereference a pointer that's
actually storing an offset, or that you might try to use a pointer that
really is a pointer, as if it were an offset. A union merely makes that
potential confusion more obvious, the potential is already there in your
current design.
You think the code you've got right now it readable, or easy to
maintain? Just the tiny part you've written is enough to give me an
upset stomach. Keep your pointers and your offsets well separated.
The standard guarantees that a pointer, converted to a sufficiently
large integer type, can be converted back to a pointer of the same type,
and the resulting pointer will compare equal to the original. (There is,
however, no guarantee that a sufficiently large integer type exists).
The standard does not, however, guarantee that an integer can be
converted to a pointer and back again, and still compare equal.
For instance, on one implementation I'm familiar with that had
segment-offset addresses, all pointers with an offset of 0 were
considered null pointers, regardless of the segment value. They
therefore, of course, all compared equal. An integer would be converted
to a pointer by putting it's high order word in the segment, and the low
order word in the offset. However, the reverse cast need not have been a
true inverse. It would be perfectly legal for such an implementation to
convert all null pointers to 0 when cast to an integer type. Think about
what that would do to your code: every offset that went in to a pointer
as an integer multiple of 65536 would come out as 0.
...
> But, the same problem happens when casting it to a (char*):
> CFromDisk* pData; // pointer to the data read from disk
> (char*)pData->mPointerOne += (long)pData;
> (char*)pData->mPointerTwo += (long)pData;
>
> I know it is ok to add a long or int to a pointer. The problem is the cast
> magically makes the pointer no longer a valid l-value.
There's nothing magical about it. A cast operation normally creates a
temporary object. Even if the code you're writing were legal, it would
describe adding (long)pData to a temporary, and not to pData->mPointer
One itself. In other words, a pretty pointless activity.
> My question is, why does casting a perfectly good pointer make the pointer
> no longer a valid l-value? Is there some way in C++ to do this, like
> reinterpret_cast?
The problem is not that you're doing it wrong, it's that you're trying
to do the wrong thing. The right thing is:
pData->mPointerOne = (T*)((char*)pData->mPointerOne + offset);
(where T is the type of *mPointerOne).
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]