![]() |
The void** pointer breaking symmetry?
Hi Clers,
If I look at my ~200000 lines of C code programmed over the past 15 years, there is one annoying thing in this smart language, which somehow reduces the 'beauty' of the source code ;-): char *cp; void *vp; void **vpp; // 1 cp=vp; // 2 cp=*vpp; Why is the first instruction allowed while the second one creates a compiler warning/error? If vpp is a pointer to a void pointer, why am I not allowed to assign the content of vpp to a char pointer without ugly explicit casts? Why is it needed to break the symmetry? Are there any GCC compiler options to specifically disable this warning which doesn't make sense to me? Thanks for your feedback, Elmar P.S.: In case this is an old question: Googles inability to search for 'void**' made it hard to find the answer ;-) |
Re: The void** pointer breaking symmetry?
elmar@cmbi.ru.nl wrote:
> If I look at my ~200000 lines of C code programmed over the past 15 > years, there is one annoying thing in this smart language, which > somehow reduces the 'beauty' of the source code ;-): > > char *cp; > void *vp; > void **vpp; > > // 1 > cp=vp; > > // 2 > cp=*vpp; > > Why is the first instruction allowed while the second one creates a > compiler warning/error? > If vpp is a pointer to a void pointer, why am I not allowed to assign > the content of vpp to a char pointer without ugly explicit casts? Why > is it needed to break the symmetry? Are there any GCC compiler options > to specifically disable this warning which doesn't make sense to me? > This looks surprisingly similar to a discussion in van der Linden's "Expert C Programming". There is a particular part in the Standard regarding assignments that talks about specific constraints. I don't have either handy right now, but the compiler error in this case feels right. |
Re: The void** pointer breaking symmetry?
elmar@cmbi.ru.nl wrote On 05/05/06 14:44,: > Hi Clers, > > If I look at my ~200000 lines of C code programmed over the past 15 > years, there is one annoying thing in this smart language, which > somehow reduces the 'beauty' of the source code ;-): > > char *cp; > void *vp; > void **vpp; > > // 1 > cp=vp; > > // 2 > cp=*vpp; > > Why is the first instruction allowed while the second one creates a > compiler warning/error? Both are legal, assuming vp has a valid value in the first instance and vpp points to a void* with a valid value in the second. Perhaps the compiler is warning about the `=*', which was an antique form of the operator now spelled `*='. If so, it's trying to tell you that `=*' no longer means what it did in the very early days of C (just in case you're compiling some very old code), and you can probably silence the warning by writing `= *' instead. There are no guarantees, though: The compiler is allowed to issue as many warnings as it wants, even for constructs that are well-defined. Most people consider this helpful in cases like if (a = b) printf ("Equal\n"); else printf ("Unequal\n"); /* Not any more ... */ .... which is a perfectly valid C fragment, but also a common slip of the finger. > If vpp is a pointer to a void pointer, why am I not allowed to assign > the content of vpp to a char pointer without ugly explicit casts? The assignment is allowed, and no cast is required. > Why > is it needed to break the symmetry? Are there any GCC compiler options > to specifically disable this warning which doesn't make sense to me? Perhaps if you'd show us "this warning" instead of making everybody guess about it, someone would have an idea. -- Eric.Sosman@sun.com |
Re: The void** pointer breaking symmetry?
elmar@cmbi.ru.nl writes:
> char *cp; > void *vp; > void **vpp; > > // 1 > cp=vp; > > // 2 > cp=*vpp; > > Why is the first instruction allowed while the second one creates a > compiler warning/error? Both are allowed. If the compiler refuses to allow one of them (possibly with a warning) then you are probably using a C++ compiler. -- "Your correction is 100% correct and 0% helpful. Well done!" --Richard Heathfield |
Re: The void** pointer breaking symmetry?
Big apologies, I accidentally pasted only the 'correct' of the two
symmetry related cases: Here is the complete code: char *cp; void *vp; void **vpp; // 1 cp=vp; // 2 cp=*vpp; // 3 vp=cp; // 4 vpp=&cp; In short: if 1 and 2 work, why does the reversion work in case 3 but fail in case 4 with the GCC message "warning: assignment from incompatible pointer type" ? Thanks again, Elmar |
Re: The void** pointer breaking symmetry?
elmar@cmbi.ru.nl wrote:
> > If I look at my ~200000 lines of C code programmed over the past 15 > years, there is one annoying thing in this smart language, which > somehow reduces the 'beauty' of the source code ;-): > > char *cp; > void *vp; > void **vpp; > > // 1 > cp=vp; > > // 2 > cp=*vpp; > > Why is the first instruction allowed while the second one creates a > compiler warning/error? > If vpp is a pointer to a void pointer, why am I not allowed to assign > the content of vpp to a char pointer without ugly explicit casts? Why > is it needed to break the symmetry? Are there any GCC compiler options > to specifically disable this warning which doesn't make sense to me? You are probably confusing errors, because the above dereferences an undefined pointer. The following is error free, and does not dereference undefined objects: int main(void) { char *cp; void *vp; void **vpp; char *s = "junk"; vp = &s[0]; cp = vp; vpp = &vp; cp = *vpp; return 0; } Always publish complete compileable source, so criticism can be meaningful. -- "If you want to post a followup via groups.google.com, don't use the broken "Reply" link at the bottom of the article. Click on "show options" at the top of the article, then click on the "Reply" at the bottom of the article headers." - Keith Thompson More details at: <http://cfaj.freeshell.org/google/> Also see <http://www.safalra.com/special/googlegroupsreply/> |
Re: The void** pointer breaking symmetry?
elmar@cmbi.ru.nl writes:
> Big apologies, I accidentally pasted only the 'correct' of the two > symmetry related cases: > > Here is the complete code: > > char *cp; > void *vp; > void **vpp; > > // 1 > cp=vp; > // 2 > cp=*vpp; > > // 3 > vp=cp; > // 4 > vpp=&cp; No, that's not the complete code. If I want to try compiling it myself, I have to wrap it in a function definition. Also, though "//" comments are legal in C99, they aren't supported by all C compilers, and they can cause problems on Usenet (wrapping of long lines can introduce syntax errors). Here's a complete program that illustrates the point: int main(void) { char *cp; void *vp; void **vpp; /* 1 */ cp=vp; /* 2 */ cp=*vpp; /* 3 */ vp=cp; /* 4 */ vpp=&cp; /* this is line 17 */ return 0; } When I compile this with gcc, I get: tmp.c: In function `main': tmp.c:17: warning: assignment from incompatible pointer type > In short: if 1 and 2 work, why does the reversion work in case 3 but > fail in case 4 with the GCC message "warning: assignment from > incompatible pointer type" ? Case 1 assigns a void* to a char*. Implicit conversion from void* to any pointer-to-object type makes this legal. Case 2 also assigns a void* to a char*. Case 3 assigns a char* to a void*. Implicit conversion from any pointer-to-object type to void* makes this legal. Case 4 assigns a char** to a void**. The implicit conversion rule applies only to void*, not to void**. In this context, a void* is just another object type, and a void** is just another pointer-to-object type. There's no implicit conversion from one pointer-to-object type to another pointer-to-object type. void* is a generic pointer type. There is no generic pointer-to-pointer type. This is similar to the fact that there's an implicit conversion between int and double, but no implicit conversion between int* and double*. If you want a generic pointer, just use a void*. For your case 4, this would be legal: vp = &cp; -- Keith Thompson (The_Other_Keith) kst-u@mib.org <http://www.ghoti.net/~kst> San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst> We must do something. This is something. Therefore, we must do this. |
Re: The void** pointer breaking symmetry?
Thanks for your detailed reply, Keith.
In my humble view, the ideal solution would be: 1) void* is a generic pointer type that can be implicitly converted to/from any other object that can be dereferenced at least !once! (e.g. char*, int*, also char**, int**, but not char,int) 2) void** is a generic pointer type that can be implicitly converted to/from any other object that can be dereferenced at least !twice! (e.g. char**, int**, also char***, int***, but not char*,int*,char,int) 3) etc... Again, I am not one of those who like to (have the time to) discuss philosophic questions about language details. This is a purely practical issue, having identified the main cause of entropy (and also crashes ;-) in my current sources: The inability to safely pass a pointer to any pointer as a function argument. Typical example: the function mem_freesetnull which frees a pointer and sets it to NULL (very helpful in the context of exception handling): Ideally, it would take the address of a pointer as argument and look like that: void mem_freesetnull(void **ptradd) { mem_free(*ptradd); *ptradd=NULL; } Unfortunately, that's not possible, because I'd have to use an ugly explicit cast to (void**) in every call to the function. So in practice, I have to move the explicit cast to the function itself: void mem_freesetnull(void *ptradd) { mem_free(*(void**)ptradd); *(void**)ptradd=NULL; } Now the function looks ugly, but more importantly, I lost an important piece of type-safety: If I accentally forget the reference operator & in the function call, noone will complain but the program will crash: char *cp; cp=mem_alloc(1000); mem_freesetnull(cp); /* Crash! */ With the approach suggested above, the compiler would immediately identify the problem, since cp cannot be dereferenced at least 2 times, as required by the ideal function declaration void mem_freesetnull(void **ptradd). In short: less code entropy and more safety in one shot. It seems that others are bothered by the same thing, since this in the FAQ: 4.9: Can I use a void ** pointer as a parameter so that a function can accept a generic pointer by reference? A: Not portably. (not sure what the answer means in this context. Does it cause problems on a VAX from 1968? ;-) I'm thinking about a GCC patch for an option to specifically disable the warning in the cases outlined above. But if that has zero chance of acceptance, I'll save my time ;-) Ciao and thanks, Elmar |
Re: The void** pointer breaking symmetry?
elmar@cmbi.ru.nl schrieb:
> Thanks for your detailed reply, Keith. Of which you unfortunately did not quote anything to provide context. > In my humble view, the ideal solution would be: > > 1) void* is a generic pointer type that can be implicitly converted > to/from any other object that can be dereferenced at least !once! > (e.g. char*, int*, also char**, int**, but not char,int) > 2) void** is a generic pointer type that can be implicitly converted > to/from any other object that can be dereferenced at least !twice! > (e.g. char**, int**, also char***, int***, but not char*,int*,char,int) > 3) etc... > > Again, I am not one of those who like to (have the time to) discuss > philosophic questions about language details. This is a purely > practical issue, having identified the main cause of entropy (and also > crashes ;-) in my current sources: The inability to safely pass a > pointer to any pointer as a function argument. The problem with this approach is that if, for example, char *foo, int *bar, and struct qux *quux have different sizes, alignment requirements and representations, then there is no way that void **baz (containing either the address of foo, bar, or quux, or of a variable containing their addresses, or ...) helps you deal with the object(s) they point to if you pass the addresses of such pointers, because dereferencing the void ** variable once gives you at least three possible interpretations of the bit pattern involved. The only safe way to obtain the address of the object would be if *baz would evaluate to a type which can contain any address that can be pointed to by any of, foo, bar and quux. One such type is void*. So, void *aux = bar; void **baz = &aux; essentially is the best you can get if you do not want the language to have to "remember" types at runtime. > Typical example: the function mem_freesetnull which frees a pointer and > sets it to NULL (very helpful in the context of exception handling): > > Ideally, it would take the address of a pointer as argument and look > like that: > > void mem_freesetnull(void **ptradd) > { mem_free(*ptradd); > *ptradd=NULL; } > > Unfortunately, that's not possible, because I'd have to use an ugly > explicit cast to (void**) in every call to the function. > > So in practice, I have to move the explicit cast to the function > itself: > > void mem_freesetnull(void *ptradd) > { mem_free(*(void**)ptradd); > *(void**)ptradd=NULL; } > > Now the function looks ugly, but more importantly, I lost an important > piece of type-safety: > If I accentally forget the reference operator & in the function call, > noone will complain but the program will crash: > > char *cp; > cp=mem_alloc(1000); > mem_freesetnull(cp); /* Crash! */ Even mem_freesetnull(&cp) works portably only due to special guarantees for char*. If you did the same for int *ip or struct qux *sp, you could run into trouble on a system where not all pointers are equal. > With the approach suggested above, the compiler would immediately > identify the problem, since cp cannot be dereferenced at least 2 times, > as required by the ideal function declaration > void mem_freesetnull(void **ptradd). Not portably if you do not want to change the information the programme must have at runtime -- and then you could change other restrictions of C as well. > In short: less code entropy and more safety in one shot. At a very high price for the language. In addition, not every place the now-stale address is stored will be "nulled" by this function -- it is more or less a false sense of safety. Realloc()ing to zero or even to a smaller size of the originally malloc()ed storage gives you similar headaches. Having a way to mark the starting address of the allocated object and all addresses inside or one past the object as trap representation with ways to find out where the whole thing trapped would be much more useful. Malloc debugging tools already do at least part of the job for you. Just add "runs cleanly under <YourToolHere> for the test set" after "compiles without warning" and "is <YourLintToolHere>-clean". > It seems that > others are bothered by the same thing, since this in the FAQ: > > 4.9: Can I use a void ** pointer as a parameter so that a function > can accept a generic pointer by reference? > > A: Not portably. > > (not sure what the answer means in this context. Does it cause problems > on a VAX from 1968? ;-) > > I'm thinking about a GCC patch for an option to specifically disable > the warning in the cases outlined above. But if that has zero chance of > acceptance, I'll save my time ;-) As I did not read what you really want to achieve (you snipped the context), I do not know whether this is the perfect solution for you or just the bad idea it seems to be... Cheers Michael -- E-Mail: Mine is an /at/ gmx /dot/ de address. |
Re: The void** pointer breaking symmetry?
elmar@cmbi.ru.nl wrote:
> Thanks for your detailed reply, Keith. > > In my humble view, the ideal solution would be: > > 1) void* is a generic pointer type that can be implicitly converted > to/from any other object that can be dereferenced at least !once! > (e.g. char*, int*, also char**, int**, but not char,int) > 2) void** is a generic pointer type [...] No; stop right there. void** is not at "generic" at all, not in the least. A void** is a pointer to a void*, and not a pointer to any other kind of object or function. Your confusion, perhaps, is this: A void* can point to any kind of object, and can be converted to and from other object pointer types without a cast. That's why it's often called "generic," but the term is really very loose. However, a void* is itself an object type, a perfectly concrete "real" object type like an int or a char* or whatever. Just as with other concrete object types, it's possible to form a pointer to objects of this void* type. But such a pointer is in no way "generic;" it can only be NULL or point to an actual void* object somewhere. > [...] having identified the main cause of entropy (and also > crashes ;-) in my current sources: The inability to safely pass a > pointer to any pointer as a function argument. That's right. C does not require that all pointers "smell the same." Pointers to different types can come in different shapes and sizes, so there's really no such thing as a "generic pointer" (despite the common sloppy usage of the phrase to describe void*). You might as well speak of a "generic number;" just as short and double can look different, short* and double* can look different. > Typical example: the function mem_freesetnull which frees a pointer and > sets it to NULL (very helpful in the context of exception handling): > Ideally, it would take the address of a pointer as argument and look > like that: > > void mem_freesetnull(void **ptradd) > { mem_free(*ptradd); > *ptradd=NULL; } > > Unfortunately, that's not possible, because I'd have to use an ugly > explicit cast to (void**) in every call to the function. Even the cast will not save you. Just as numbers come in different flavors, pointers come in different flavors. Just as you cannot set a number to zero without knowing its type, you cannot set a pointer to NULL without knowing its type. > So in practice, I have to move the explicit cast to the function > itself: > > void mem_freesetnull(void *ptradd) > { mem_free(*(void**)ptradd); > *(void**)ptradd=NULL; } > > Now the function looks ugly, but more importantly, I lost an important > piece of type-safety: Most important of all, the function is now incorrect. You seem upset by all the warnings the compilers emit for constructs of this sort, but it turns out they know C better than you do: This code is wrong, and the compiler is right to complain about it. > I'm thinking about a GCC patch for an option to specifically disable > the warning in the cases outlined above. But if that has zero chance of > acceptance, I'll save my time ;-) While you're at it, disable the diagnostics for unbalanced parentheses, for `("Hello" / "world!")', and for all the other programming errors that might be made. The source for gcc is readily available; you are free to make changes and use your own version if you choose -- but allow me to suggest that your choice is ill-informed. In short, you don't know what you're doing; you don't know C well enough. -- Eric Sosman esosman@acm-dot-org.invalid |
| All times are GMT. The time now is 09:01 AM. |
Powered by vBulletin®. Copyright ©2000 - 2013, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.