max_size defines the maximum size a container theoretically can have for the particular implementation of that container.
That number does not depend on the OS or the available memory but is only given by the implementation of the container.
If your (not std::string compatible) implementation of a string container is like this:
struct string {
unsigned char size;
char *data;
// … further functions …
};
Then max_size would likely refer to the largest number unsigned char can represent.
If your implementation is just a \0 terminated string, without any other meta information. Then max_size might refer to the maximum number of bytes that can be addressed through pointers, for the given target architecture.
So max_size just says, the way the container is implemented would be able to handle max_size number of elements. But it does not guarantee that the OS is capable of that.
For an std::string the upper bounds of the maximum number of chars the implementation can handle is given by a maximum number of size_type and some further constraints.
The size_type for a std::string itself is given by the used allocator (std::allocator<CharT>) which defaults to std::allocator_traits<Allocator>::size_type.
For a std::allocator the size_type referees to std::size_t.
So for a std::string the upper bound of max_size is the max value of std::size_t minus a value n that is required to fulfill the other requirements of the string.
The libstdc++ of gcc-4.6.2 defines says this about the max_size:
// The maximum number of individual char_type elements of an
// individual string is determined by _S_max_size. This is the
// value that will be returned by max_size(). (Whereas npos
// is the maximum number of bytes the allocator can allocate.)
// If one was to divvy up the theoretical largest size string,
// with a terminating character and m _CharT elements, it'd
// look like this:
// npos = sizeof(_Rep) + (m * sizeof(_CharT)) + sizeof(_CharT)
// Solving for m:
// m = ((npos - sizeof(_Rep))/sizeof(CharT)) - 1
// In addition, this implementation quarters this amount.
static const size_type _S_max_size;
static const _CharT _S_terminal;
and that corresponding initialization
template<typename _CharT, typename _Traits, typename _Alloc>
const typename basic_string<_CharT, _Traits, _Alloc>::size_type
basic_string<_CharT, _Traits, _Alloc>::
_Rep::_S_max_size = (((npos - sizeof(_Rep_base))/sizeof(_CharT)) - 1) / 4;
According to this 197. max_size() underspecified (not sure if there is any newer update to that), the value of max_size won't change from call to call:
It is clear to the LWG that the value returned by max_size() can't change from call to call.
So you can use the approach of eerorika to get that value for a specific allocator.