tags:

views:

2200

answers:

9

In an application I maintain, we've encountered a problem with file descriptor limitations affecting the stdlib. This problem only affects the 32-bit version of the standard lib.

I have devised a fix for my code and would like to implement it, but only when compiling for 32-bit executable. What pre-processor symbol can I #ifdef for to determine whether the code is being compiled for a 32 or 64-bit target?

EDIT

Sorry, didn't mention, the code is cross-platform, linux, windows, solaris and a few other unix flavors, mostly using GCC for compilation. Any de-facto standards I can use cross-platform?

EDIT 2

I've found some definitions "__ILP23" and "__LP64" that seem like they may work... a discussion here explains the background on the unix platform. Anyone had any experience with using these defines? Is this going to be usable?

+6  A: 

I'm not sure if there is a universal #if def that is appropriate. The C++ standard almost certainly does not define one. There are certainly platform spcefic ones though.

For example, Windows

#if _WIN64 
// 64 bit build
#else
// 32 bit build
#endif

EDIT OP mentioned this is a cross compile between Windows and Non-Windows using GCC and other compilers

There is no universal macro that can be used for all platforms and compilers. A little bit of preprocessor magic though can do the trick. Assuming you're only working on x86 and amd64 chips the following should do the trick. It can easily be expanded for other platforms though

#if _WIN64 || __amd64__
#define PORTABLE_64_BIT
#else
#define PORTABLE_32_BIT
#endif
JaredPar
Ok, sounds like this, combined with some gcc investigation per Chris' comments may do the trick. +1 both of you.
veefu
+3  A: 

You could check a well known type for it's size e.g. sizeof(int*) == 4 for a 32 bit platform.

As sizeof is known at compiletime I believe a

if(sizeof(int*) == 4)
{
  ...
}

should do the trick

Edit: the comments are right, you need to use a regular if, #if won't work.

If you are using C++ You could create templated code and let the compiler choose the specialization for you based on the sizeof() call. If you build for a 32 bit platform the compiler would only instantiate the code for the 32 bit platform. If you build for a 654 bit platform the compiler would only instantiate the code for the 64 bit platform.

lothar
This won't work for an #ifdef though. sizeof is compile time while #if is pre-processor time
JaredPar
that's invalid code (tested on VS2008)
JaredPar
Agreed. Tested on GCC 4.0.1 on OS X Leopard.
Chris Lutz
Create a program that runs a test as part of the build process, and that outputs some #defines into a config file or Makefile?
Thomas Padron-McCarthy
This would include both 32 bit and 64 bit code in the same executable/library. I dont like this approach, because it depends on a pointer being 4 or whatever bytes long. A bad practice.
Jens
Isn't that the point? 4-bytes for 32-bit 8 for 64?
Andy
If you build for only a 32 bit platform the compiler would only instantiate the code for the 32 bit platform. The whole point about the dependency if a pointer is 4 or 8 bytes is to distinguish a 32bit from a 64 bit platform
lothar
The C++ standard says an int is "the natural size" for the platform, i.e., 32 bits on a 32 bit platform, and 64 bits on a 64 bit platform. This is something you can rely on for all standard-conforming platforms. Use template specialization and you've removed a macro. +1 from me.
Max Lybbert
Actually the reason I picked the size of the pointer is that it is more reliable than the size of the int itself.
lothar
Thinking it over I think I would go with sizeof(void*).
Max Lybbert
Sure, why not. Any pointer will do :-)
lothar
I just mean I agree that using pointers (1) communicates what you're looking for better, and (2) is guaranteed to work even on a platform that understands "natural size" differently than I do ( http://www.unix.org/version2/whatsnew/lp64_wp.html ).
Max Lybbert
A: 

I believe the define is _WIN64

Arnshea
+5  A: 

I recommend bookmarking the predef SourceForge. There's no one answer, but it can certainly help you get started.

EDIT: For GCC-only code, you can use __i386__ to check for 32-bit x86 chips, and I suggest trying __X86_64__ or something similar to check for 64-bit x86 chips. (Note: It has come to my attention that the previous answer involving __ia86__ is actually a different chip, not a 64-bit x86 chip. This just shows my lack of hardware experience. For those more knowledgeable about hardware than I, consule the SourceForge page on predefined macros that I link to above. It's much more accurate than I am.) There are some other ones that would work, but those two should be fairly universal amongs GCC versions.

Chris Lutz
Will __ia64__ be true for a normal intel64 bit compile? Or is there a __amd64__ or the like?
JaredPar
I have no experience with 64-bit architectures (or with programming on non-Intel architectures), but according to the predefs page, there is an __amd64__ for 64-bit AMD. __ia64__ appears to be x86-specific.
Chris Lutz
__i386__ would only work when compiling for intel cpu, correct? On solaris we compile for sparc-s2.
veefu
As another tip, you can run `gcc -E -dM - </dev/null` (or leave of the `</dev/null` and just type EOF after you press enter), and GCC will spit out a list of all the macros it has pre-defined. Do this on a few platforms, and compile a list of the unique and useful ones for each platform.
Chris Lutz
@veefu - Yes, SPARC chips will have a different macro. Most likely, you can use __sparc__ or something similar. There are about three similar but different macros for each platform that you can use, especially for GCC. Check the predefs page.
Chris Lutz
@Chris: thanks, that's a great tip.
veefu
__ia64__ will not be defined for x86, x64, or amd64 builds - the ia64 is a completely different CPU architecture that has no relationship to x86 (except that Intel was involved in its development).
Michael Burr
@Chris nice resoruce!
Daniel A. White
+1  A: 

Depends on your OS and compiler, those are implementation decisions.

Charlie Martin
A: 

There is no such symbol defined by the C++ standard - your specific platform (which you haven't specified in your question) may provide one.

anon
+2  A: 

What I would probably end up doing, is within a Makefile, determine if you are on a 32 bit platform or 64 bit using uname. Then, add to your CFLAGS, -DX32, or -DX64. That you could just #ifdef X64.

But this is just a unixy solution. I'm not sure what I would do on windows.

sharth
The trouble with that is even though the system being used for compilation is 64-bit, it may be compiling a 32-bit executable. You are right, though, that this information must be exposed somehow in the makefile. The compiler must be told whether 32 or 64 is targetted. Should be able to adapt that.
veefu
+1  A: 

Have a look at that:

i386 macros
AMD64 macros

Bastien Léonard
good information there, thanks.
veefu
+1  A: 

At least 32-bit Solaris has a limit of 256 file pointers because the structure stores the file descriptor in an unsigned char field. This is retained for backwards compatibility with some almost impossibly old versions of SunOS. Other platforms - I'm tempted to say most other platforms - do not share that limitation. On the other hand, it is relatively unusual for an ordinary user program to need that many files open concurrently; it more often indicates a bug (not closing the files when finished with them) than not. Having said that, though, it can be a problem for things like database servers which need to have lots of data files open at the same time.


One comment says:

That's almost it. We don't have a large number of files open, but the server handles a large number of connections from clients. Socket handles and file descriptors seem to come from the same place. When we have a lot of connections, 'fopen' fails because the system-level call returns and fd > 255.

'Socket handles' are file descriptors at the system call level, so they come from the same place as regular file descriptors for files.

If you have to work around this, then you need to wrap your current socket opening code so that if it gets an file descriptor in the range 0..255, then it calls 'dup2()' to create a file descriptor in the range that stdio won't use - and then close the original file descriptor. The only snag with this is that you have to keep track of which file descriptors are available, because dup2 will merrily close the target file descriptor if it is currently open.

Of course, I'm assuming your socket code reads file descriptors and not file pointers. If that's the case, you have bigger problems - too many things want to use the same resources and they can't all use them at the same time.

Jonathan Leffler
That's almost it. We don't have a large number of files open, but the server handles a large number of connections from clients. Socket handles and file descriptors seem to come from the same place. When we have a lot of connections, 'fopen' fails because the system-level call returns and fd > 255.
veefu
+1 for insightful, though.
veefu
Yes, that's almost exactly what I've implemented. wrapped calls to 'socket' and 'accept' with code that calls 'fcntl' to duplicate the handle when < 255, close the original, and use the higher one. This works well, but must be isolated to the platform its needed. Hence question about #ifdef.
veefu