I was fixing up a bunch of my osdev code and for some reason my warning options were throwing errors on conversions of types and I couldn't figure out why when I didn't enable -Wconversion. After a bit of trying to track the problem down, I decided to just use -Wno-conversion to disable them as a quick fix but after thinking about it a bit, I didn't like that solution. So last night, I spent quite a bit trying to wrap my head around the error and trying to figure out where the issue was. I came to the conculsion that I could type cast the type and get it to work and thus solving my issue. While I thought this was a much better solution, I wondered if casting was not right and how many casts were too much. After doing a bit of research, I noticed people with different answers to the matter, some suggested some casts are ok as long as not too many and others said any casts were bad and that was an indication of the person trying to do something with the code and not doing it the right way.
So I was wondering what are peoples opinions on the subject? Is casting bad, is it ok in some aspects?
My background consists of college and mainly self-taught practices and not having any actual real world coding work yet, so it would be interesting to get not only a real world answer but also from an osdev one. Is it even worth considering?
Are type casts ok in code?
- beyondsociety
- Member
- Posts: 39
- Joined: Tue Oct 17, 2006 10:35 pm
- Location: Eagle, ID (USA)
- Contact:
Are type casts ok in code?
"I think it may be time for some guru meditation"
"Barbarians don't do advanced wizardry"
"Barbarians don't do advanced wizardry"
Re: Are type casts ok in code?
In regular (client) code you should not need many casts, and any that you do use are suspicious.
In C++ terms:
dynamic_cast is 100% avoidable and should be avoided if possible, especially in any real-time systems (as they are unbounded!).
const_cast indicates an error in your const-ness of things, where you at some point do not uphold the constness of an object that you did indicate before. It should be fixed but in large enough projects can be excused (as in some cases it can lead to a 1000-file chase of constness).
reinterpret_cast indicates you have the wrong type for something or you're doing something crazy. Stop doing that. "Crazy" is compared to regular application development. Many times I've seen people use this for deserialization of types - you should never do that with any kind of unaligned read or pointer cast, do your byte-to-X conversion by hand and don't have any endian-ness problems or BUS_ERROR ever.
static_cast is a cast that should usually be OK but can still be questioned - why do you cast? Why did you lose the type at some point and can you avoid having to do this cast, if you know ahead of time it'll be OK all the time?
For C code, it's the same except that a C cast can be any of them - except you have to guess which one.
For OS dev specifically though, in some places (printf, operator new, std::vector implementation) you can need these kinds of weird constructs. Don't hesitate to use them, but always try to limit them and use the weakest form you can use.
In C++ terms:
dynamic_cast is 100% avoidable and should be avoided if possible, especially in any real-time systems (as they are unbounded!).
const_cast indicates an error in your const-ness of things, where you at some point do not uphold the constness of an object that you did indicate before. It should be fixed but in large enough projects can be excused (as in some cases it can lead to a 1000-file chase of constness).
reinterpret_cast indicates you have the wrong type for something or you're doing something crazy. Stop doing that. "Crazy" is compared to regular application development. Many times I've seen people use this for deserialization of types - you should never do that with any kind of unaligned read or pointer cast, do your byte-to-X conversion by hand and don't have any endian-ness problems or BUS_ERROR ever.
static_cast is a cast that should usually be OK but can still be questioned - why do you cast? Why did you lose the type at some point and can you avoid having to do this cast, if you know ahead of time it'll be OK all the time?
For C code, it's the same except that a C cast can be any of them - except you have to guess which one.
For OS dev specifically though, in some places (printf, operator new, std::vector implementation) you can need these kinds of weird constructs. Don't hesitate to use them, but always try to limit them and use the weakest form you can use.
Re: Are type casts ok in code?
If you use a cast to make your code compile and don't really understand why the compiler is complaining, it's wrong. If you do it fully knowing why the cast is required, then use it (since it is required).
If you are programming in C++, do use the right cast as the C++ casts document why you are casting.
If you are programming in C++, do use the right cast as the C++ casts document why you are casting.
Re: Are type casts ok in code?
There are a number of situations, where you'd use casts:
- your compiler's warning level is too high (it's almost always something you want, but it may come with this price of needing to cast something here and there explicitly)
- there's some hardware-specific craziness like needing to store an address and some other info together in a hardware/system register or table (e.g. CR3, PTEs) and you can't separate the address from the rest without doing some bit manipulations in integer types, thus necessitating trips from pointers to integers and back
- some typical things like checking address alignment can also be done only in integer types
- there are crazy things in C as well: it's illegal to compare two arbitrary pointers using relational operators (<,<=,>,>=), which is why most implementations of memmove() that do it anyway are legally broken or at the very least are not portable, so you may need another trip to integers here
- there are less crazy things in C, but nonetheless getting in the way: there's no pointer arithmetic with pointers to void, so you need to cast them to pointers to char or to integers in order to compare them (with <,<=,>,>=) or increment/decrement
There are a few situations where casts are wrong:
- you're misusing types, perhaps because you don't know them well enough (e.g. can't declare properly), e.g. you define your IDT/IVT/syscall table as an array of pointers to data (void* idt[256] or char* idt[256]) and then you actually attempt to call those functions and suddenly you are required to cast idt[index] to something like (void(*)(void)) in order to be able to make the call; and all you had to do is make a proper declaration, e.g. void (*idt[256])(void)
- you're violating aliasing rules, it's an important thing these days since modern compilers can make your code fail due to violated aliasing rules, but I'm not going to discuss this topic here, there are discussions, flame wars, articles and so on on it.
I'm sure I'm missing a few other cases. But there clearly are cases, where you're required to do some kind of type casting (or need to shut the compiler up here and there without shutting it up entirely and thus losing its hints at potential bugs), and there are cases, where doing it can be harmful or is just plain unnecessary.
- your compiler's warning level is too high (it's almost always something you want, but it may come with this price of needing to cast something here and there explicitly)
- there's some hardware-specific craziness like needing to store an address and some other info together in a hardware/system register or table (e.g. CR3, PTEs) and you can't separate the address from the rest without doing some bit manipulations in integer types, thus necessitating trips from pointers to integers and back
- some typical things like checking address alignment can also be done only in integer types
- there are crazy things in C as well: it's illegal to compare two arbitrary pointers using relational operators (<,<=,>,>=), which is why most implementations of memmove() that do it anyway are legally broken or at the very least are not portable, so you may need another trip to integers here
- there are less crazy things in C, but nonetheless getting in the way: there's no pointer arithmetic with pointers to void, so you need to cast them to pointers to char or to integers in order to compare them (with <,<=,>,>=) or increment/decrement
There are a few situations where casts are wrong:
- you're misusing types, perhaps because you don't know them well enough (e.g. can't declare properly), e.g. you define your IDT/IVT/syscall table as an array of pointers to data (void* idt[256] or char* idt[256]) and then you actually attempt to call those functions and suddenly you are required to cast idt[index] to something like (void(*)(void)) in order to be able to make the call; and all you had to do is make a proper declaration, e.g. void (*idt[256])(void)
- you're violating aliasing rules, it's an important thing these days since modern compilers can make your code fail due to violated aliasing rules, but I'm not going to discuss this topic here, there are discussions, flame wars, articles and so on on it.
I'm sure I'm missing a few other cases. But there clearly are cases, where you're required to do some kind of type casting (or need to shut the compiler up here and there without shutting it up entirely and thus losing its hints at potential bugs), and there are cases, where doing it can be harmful or is just plain unnecessary.
- AndrewAPrice
- Member
- Posts: 2299
- Joined: Mon Jun 05, 2006 11:00 pm
- Location: USA (and Australia)
Re: Are type casts ok in code?
I've wondered about this - a language that never lets you cast down, but you're allowed to cast up.
You can cast Cat to Animal, but never Animal to Cat.
I like how Go does it with a type switch.
You could also imitate a type switch with an interface in pretty much any OO language without needing resorting to downcasting. Let's say you had an interface of all handlers for each animal type: (psuedo code)
Then define your animals like this:
Then if you want to cast your animal or get it's type, you could do:
You can cast Cat to Animal, but never Animal to Cat.
I like how Go does it with a type switch.
Code: Select all
switch animal := animal.(type) {
default:
// unknown animal
case *Cat:
// animal is now Cat
case *Dog:
// animal is now Dog
}
Code: Select all
interface AnimalTypes {
void IsCat(Cat *c);
void IsDog(Dog *d);
}
Code: Select all
class Animal {
void GetAnimalType(AnimalTypes *handler);
}
class Cat : Animal {
void GetAnimalType(AnimalTypes *handler) {
handler->IsCat(this);
}
}
class Dog : Animal {
void GetAnimalType(AnimalTypes *handler) {
handler->IsDog(this);
}
}
Code: Select all
void MakeSound(Animal *animal) {
animal->GetAnimalType(new AnimalTypes {
void IsCat(Cat *c) {
printf("Meow!");
}
void IsDog(Dog *d) {
printf("Woof!");
}
});
}
My OS is Perception.