surega
surega

Reputation: 715

passing an object to var arg function

How does a variable arguement function take object to struct/class? For example :

CString a_csName;
CString a_csAge(_T("20"));
a_csName.Format(_T("My Age is : %s"),a_csAge);

Here CString::Format is a printf-style variable arguement function which takes this CString object. How is this possible?

Upvotes: 2

Views: 616

Answers (4)

Ajay
Ajay

Reputation: 18429

Let me start answering this one differently.

struct Value
{
   int nValue;
   float fOtherValue;
};

int main()
{
    Value theValue;
    theValue.nValue = 220;
    theValue.fOtherValue = 3.14f;

    printf("Value: %d", theValue);
}

This code will print 220 without any issues. But if you pass second argument after theValue, it wont:

printf("Values: %d, %f", theValue, theValue.fOtherValue);

Since the first variable argument theValue doesn't meet the size specified by %d argument. Hence, 3.14 wont (may not) be displayed. Let's not talk how printf, argument pushing on stack, va_start and similar things work.

Similarly, when we design a String class this way:

struct String
{
    char* pData;

public:
    String()
    {
        pData = new char[40];
        strcpy_s(pData, 40, "Sample");
    }
};

And use it this way:

String str;
printf("%s", str);

It would definitely work.

But what about argument and call-stack corruption? What if size of a class (like Value) is more than the data size (4 for Value) as specified by format (%d). Well, in that case, the class designed need to ensure that size of given class is same as argument-size being used by printf function.

I wont mention the details about it, but CString uses internal class CStringData. The latter class holds the string, length, reference count etc. CString (actually CSimpleStringT) uses this class, but hold only the pointer of char/wchar_t, managed by CStringData.

Largely, CString is implemented as the String class mentioned (as far as data and sizeof goes).

Upvotes: 0

André Puel
André Puel

Reputation: 9189

From: http://msdn.microsoft.com/en-us/library/aa300688%28v=vs.60%29.aspx

  • You can freely substitute CString objects for const char* and LPCTSTR function arguments.

Probably CString is a class without vtable, and with only a attribute of type char*. This means that sizeof(CString) == sizeof(const char*) and that if you reinterpret_cast a CString to a const char* you will get a working const char*.

The compiler should not accept passing a struct to the variadic part of the function. But if I am not wrong, in GCC, when you pass a struct to as variadic argument, it's memory is copied (i.e. no copy constructor is used) and a warning is issued. I guess the MSVC is doing the same there, and then, the Format method is just assuming the given data is a const char*.

Let me give you a alternative example. Suppose you have a class without vtable which only members is a int. If you pass it to a variadic argument, the compiler would copy this objects contents, which is only an int. On the function implementation, you (somehow) know that you received an int. Then you query the parameter asking for an int. Since, at memory level, your class is nothing different than a int, things will work "fine". The function would access the int attribute of the class.

Upvotes: 4

user1982127
user1982127

Reputation: 21

I recently had to clear this Lint error and stumbled on this thread.

Although it's an interesting investigation into the cast that is occurring, the simplest way to avoid the Lint warning is to pass the CString pointer and not the CString structure by using the CString method Get.String() as in the following

a_csName.Format(_T("My Age is : %d"), a_csAge.GetString());

Upvotes: 2

surega
surega

Reputation: 715

After a bit of researching and debugging through MFC code found the below, hope it helps whoever faces the ever famous static code analyser error "Passing struct 'CStringT' to ellipsis" which actually is the source of my doubt as well.

http://www.gimpel.com/html/bugs/bug437.htm

Format function being a variadic function, depends on the format specifier present in the first parameter. First parameter is always a char *.

It parses for the format specifier(%s,%d,%i…) and read the var_arg array based on the index of the format specifier found and does a direct cast to char * if %s or int if %d is specified.

So if a CString is specified and corresponding format specifier is %s, then a direct cast attempt to char * is made on the CString object.

CString a_csName;
CString a_csAge(_T("20"));
a_csName.Format(_T("My Age is : %s"),a_csAge);
wcout<<a_csName;

Would print My Age is 20

CString a_csName;
CString a_csAge(_T("20"));
a_csName.Format(_T("My Age is : %d"),a_csAge);
wcout<<a_csName;

Would print My Age is 052134

So there is no intelligence behind this. Just a direct cast. Hence we pass a POD or User-Defined Data structure it doesn’t make a difference.

Upvotes: 4

Related Questions