How to solve Variadic Arguments issue (using C): Difference between revisions

From Try-AS/400
Jump to navigation Jump to search
(Restructured a bit.)
 
(31 intermediate revisions by one other user not shown)
Line 1: Line 1:
{{FIXME|Rephrase, reformat, set links and wade out unneccessary stuff.}}
First of all, what are Variadic Arguments (VA)?
First of all, what are Variadic Arguments (VA)?
It's a way to NOT define the number of arguments a function (or method) takes. You only define the args you '''definitely''' going to be given on '''every''' call. All additional arguments are then optional, but can be used if given.
It's a way to NOT define the number of arguments a function (or method) takes. You only define the args you '''definitely''' going to be given on '''every''' call. All additional arguments are then optional, but can be used if given.
Line 5: Line 6:


== The Issue ==
== The Issue ==
I<ref>[[User:Heiko]]</ref> have some code that
=== The code used ===
I<ref>[[User:Heiko]]</ref> have some code that I have been using for years in macOS and Linux projects. It's purpose is to log messages with the source's filename, line number and some custom message, depending on the severity/log level.
 
This is how it's used in the code:
// TEST W/ ADDITIONAL variadic arguments
Say(0, "Error: Socket creation failed with %s\n", "just a random argument");
// EMPTY variadic arguments
Say(0, "Error: Socket creation failed...\n");
 
This is how the define adds the filename and linenumber:
#define Say(logLevel, parameter, ...) logWithMethodName(__FUNCTION__, __LINE__, logLevel, (parameter), ##__VA_ARGS__);
 
This is the actual logging code (you can skip that, it's not relevant, except for the parameters):
void logWithMethodName(const char* methodName, int lineNumber, int logLevel, char *format, ...)
{
if (gLogLevel>=logLevel)
{
/* Variables */
char msg1[kMaxMsgLen];
char msg2[kMaxMsgLen];
char lMethodName[kMaxMethodLen];
/* Get the arguments */
va_list argp;
va_start(argp, format); // format being the last argument before '...'
/* Combine format and arguments! */
vsprintf(msg2, format, argp);
if (strlen(methodName) > kMaxMethodLen)
{
memcpy(lMethodName, methodName, kMaxMethodLen-1);
lMethodName[kMaxMethodLen]='\0';
}
else
{
strcpy(lMethodName, methodName);
}
if (strlen(format) > kMaxFormatLen) format[kMaxFormatLen]='\0';
sprintf(msg1, "%s (%d): ", lMethodName, lineNumber);
strcat(msg1, msg2);
/* Output to stdout */
if (1)
{
printf(msg1);
}
/* Write to syslog */
if (0)
{
//logger(msg1);
}
va_end (argp);
}
return;
}


== The Solution ==
== The Solution ==
Looks fine, you say? I'd agree. This code worked for me for years. Then I compiled it on our AS/400...
<code>CRTCMOD MODULE(CTI/NTWRKNG) SRCFILE(CTI/SOURCES) SRCMBR(NETWORKING) OPTION(*LOGMSG) OUTPUT(*PRINT)</code><ref>Note the OUTPUT-Option! If not given, you will get only <code>Module NTWRKNG is not created because statement errors occurred.</code> info, and no further indication of what went wrong. With <code>OUTPUT(*PRINT)</code> given, you will receive detailed information in your print queue.</ref>
...and ran into these error messages:
35 ! // TEST W/ ADDITIONAL variadic arguments !
36 ! Say(0, "Error: Socket creation failed with %s\n", "just a random argument");
36 + logWithMethodName("connectAMI", 36, 0, (paramete;                  <font color="red"><--- Note the truncated line! (but the ; is there!)</font>
SEVERE==========> a - CZM0277 Syntax error: possible missing ')' or ','? <font color="red"><--- Obviously, this truncated line misses some characters...</font>
SEVERE==========> b - CZM0045 Undeclared identifier paramete.            <font color="red"><--- Obviously, this truncated identifier won't work...</font>
37 !
38 ! // EMPTY variadic arguments
39 ! Say(0, "Error: Socket creation failed...\n");
39 + logWithMethodName("connectAMI", 39, 0, (paramete;                  <font color="red"><--- Note the truncated line! (but the ; is there!)</font>
ERROR===========> a - CZM0041 The invocation of macro Say contains fewer arguments than are required by the macro definition. <font color="red"><--- What? It seems there HAS to be a variadic argument. Giving none won't work with this compiler<ref>I tried several compiler settings (f.e. LANGLVL), and even using __VA_OPT__.</ref></font>
SEVERE==========> b - CZM0277 Syntax error: possible missing ')' or ','? <font color="red"><--- Obviously, this truncated line misses some characters...</font>


So what next? I couldn't explain why and how the truncation happened (even after many attempts of understanding/solving this issue) and started to follow the only other lead: The CZM0041 error, even though was only thrown by one of the two lines.


[https://en.wikipedia.org/wiki/Variadic_macro Wikipedia] states: ''Both the [C99] and [C++11] standards '''require at least one argument''', but since [C++20] this limitation has been lifted through the __VA_OPT__ functional macro. The __VA_OPT__ macro is replaced by its argument when arguments are present, and omitted otherwise. <font color="green">Common compilers also permit passing zero arguments before this addition, however.</font>''


The <font color="green">green sentence</font> meant, that all previously used compilers permitted zero arguments, but the AS/400's compiler didn't.


So I changed my define from...
#define Say(logLevel, parameter, ...) logWithMethodName(__FUNCTION__, __LINE__, logLevel, (parameter), ##__VA_ARGS__);
...to...
#define Say(logLevel, ...) logWithMethodName(__FUNCTION__, __LINE__, logLevel, __VA_ARGS__);
Meaning, only the logLevel (severity) argument was defined, everything else, including the always following text message, was part of the VA. This way, VA would always include at least one argument.


== Weblinks ==
* [https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html GNU.org explaining VA]]


== Footnotes ==
<references />


----
[[Category: Programming]]
* Links
** https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html GNU.org explaining VA

Latest revision as of 21:46, 23 February 2020

Qsicon Fixme.png This article isn't finished yet or needs to be revised. Please keep in mind that thus it may be incomplete.

Reason: Rephrase, reformat, set links and wade out unneccessary stuff.

First of all, what are Variadic Arguments (VA)? It's a way to NOT define the number of arguments a function (or method) takes. You only define the args you definitely going to be given on every call. All additional arguments are then optional, but can be used if given.

While porting C-code from Linux to AS/400 I ran into a problem. This article is going to explain the issue and the solution.

The Issue

The code used

I[1] have some code that I have been using for years in macOS and Linux projects. It's purpose is to log messages with the source's filename, line number and some custom message, depending on the severity/log level.

This is how it's used in the code:

// TEST W/ ADDITIONAL variadic arguments
Say(0, "Error: Socket creation failed with %s\n", "just a random argument");

// EMPTY variadic arguments
Say(0, "Error: Socket creation failed...\n"); 

This is how the define adds the filename and linenumber:

#define Say(logLevel, parameter, ...) logWithMethodName(__FUNCTION__, __LINE__, logLevel, (parameter), ##__VA_ARGS__);

This is the actual logging code (you can skip that, it's not relevant, except for the parameters):

void	logWithMethodName(const char* methodName, int lineNumber, int logLevel, char *format, ...)
{
		if (gLogLevel>=logLevel)
		{
			/* Variables */
			char msg1[kMaxMsgLen];
			char msg2[kMaxMsgLen];
			char lMethodName[kMaxMethodLen];
			
			/* Get the arguments */
			va_list argp;
			va_start(argp, format); // format being the last argument before '...'

			/* Combine format and arguments! */
			vsprintf(msg2, format, argp);

				if (strlen(methodName) > kMaxMethodLen)
				{
					memcpy(lMethodName, methodName, kMaxMethodLen-1);
					lMethodName[kMaxMethodLen]='\0';
				}
				else
				{
					strcpy(lMethodName, methodName);
				}

				if (strlen(format) > kMaxFormatLen) format[kMaxFormatLen]='\0';
				
				sprintf(msg1, "%s (%d): ", lMethodName, lineNumber);
				strcat(msg1, msg2);

				/* Output to stdout */
				if (1)
				{
					printf(msg1);
				}


				/* Write to syslog */
				if (0)
				{
					//logger(msg1);
				}
			
			va_end (argp);
		}
	
	return;
}

The Solution

Looks fine, you say? I'd agree. This code worked for me for years. Then I compiled it on our AS/400...

CRTCMOD MODULE(CTI/NTWRKNG) SRCFILE(CTI/SOURCES) SRCMBR(NETWORKING) OPTION(*LOGMSG) OUTPUT(*PRINT)[2]

...and ran into these error messages:

35 ! // TEST W/ ADDITIONAL variadic arguments ! 
36 ! Say(0, "Error: Socket creation failed with %s\n", "just a random argument");
36 + logWithMethodName("connectAMI", 36, 0, (paramete;                   <--- Note the truncated line! (but the ; is there!)
SEVERE==========> a - CZM0277 Syntax error: possible missing ')' or ','? <--- Obviously, this truncated line misses some characters...
SEVERE==========> b - CZM0045 Undeclared identifier paramete.            <--- Obviously, this truncated identifier won't work...
37 !
38 ! // EMPTY variadic arguments
39 ! Say(0, "Error: Socket creation failed...\n"); 
39 + logWithMethodName("connectAMI", 39, 0, (paramete;                   <--- Note the truncated line! (but the ; is there!)
ERROR===========> a - CZM0041 The invocation of macro Say contains fewer arguments than are required by the macro definition. <--- What? It seems there HAS to be a variadic argument. Giving none won't work with this compiler[3]
SEVERE==========> b - CZM0277 Syntax error: possible missing ')' or ','? <--- Obviously, this truncated line misses some characters...


So what next? I couldn't explain why and how the truncation happened (even after many attempts of understanding/solving this issue) and started to follow the only other lead: The CZM0041 error, even though was only thrown by one of the two lines.

Wikipedia states: Both the [C99] and [C++11] standards require at least one argument, but since [C++20] this limitation has been lifted through the __VA_OPT__ functional macro. The __VA_OPT__ macro is replaced by its argument when arguments are present, and omitted otherwise. Common compilers also permit passing zero arguments before this addition, however.

The green sentence meant, that all previously used compilers permitted zero arguments, but the AS/400's compiler didn't.

So I changed my define from...

#define Say(logLevel, parameter, ...) logWithMethodName(__FUNCTION__, __LINE__, logLevel, (parameter), ##__VA_ARGS__);

...to...

#define Say(logLevel, ...) logWithMethodName(__FUNCTION__, __LINE__, logLevel, __VA_ARGS__);

Meaning, only the logLevel (severity) argument was defined, everything else, including the always following text message, was part of the VA. This way, VA would always include at least one argument.

Weblinks

Footnotes

  1. User:Heiko
  2. Note the OUTPUT-Option! If not given, you will get only Module NTWRKNG is not created because statement errors occurred. info, and no further indication of what went wrong. With OUTPUT(*PRINT) given, you will receive detailed information in your print queue.
  3. I tried several compiler settings (f.e. LANGLVL), and even using __VA_OPT__.