Null-terminating a list of arguments for a C variadic function - c

I'm messing around with variadic functions in C to learn how they work, and am trying to build a simple 'print lines' function without requiring manual counting of the lines. I'm doing this by wrapping the function in a macro that adds a null pointer to the end of a list of char * arguments, so the function can print line-by-line until a null argument is found.
I know I've avoided some common pitfalls, like forgetting to cast the null pointer in the argument list, but for whatever reason this code still isn't working. Calling the function with any number of parameters prints them properly, then fails to detect the null, prints a bunch of garbage data, and crashes.
int printline(const char *str) {
printf("%s\n", str);
}
#define printlines(...) _comments(__VA_ARGS__, (char*)0)
int _printlines(char* first, ...) {
if (first) {
printline(first);
va_list ptr;
va_start(ptr, first);
char *next;
do {
char *next = va_arg(ptr, char *);
if (next) {
printline(next);
}
} while(next);
va_end(ptr);
}
}
int main() {
printlines("hi");
//prints 'hi', then prints garbage data and crashes
printlines("how", "are", "you");
//prints 'how', 'are', and 'you', then prints garbage data and crashes
_printlines("help", (char *)0);
//prints 'help', then prints garbage data and crashes
_printlines("something", "is", "wrong", (char *)NULL);
//prints 'something', 'is', and 'wrong', then prints garbage data and crashes
}

If you take a look at this:
char* next;
do{
char* next = va_arg(ptr,char*);
if(next){ comment(next); }
}while(next);
You'll see that you have two separate variables called next, with the one inside of the do..while loop masking the one defined outside. You're assigning the result of va_arg to the inner next. Then when you get the while (next) condition, the inner next is out of scope and you're now reading the outer next which was never written to. This triggers undefined behavior.
You instead want:
char* next;
do{
next = va_arg(ptr,char*);
if(next){ comment(next); }
}while(next);
So that you only have a single variable called next that you're using.

Small rewrite. The macro has been modified with a +0 so it can take zero arguments.
#include <stdio.h>
#include <stdarg.h>
#define printlines(...) _printlines(__VA_ARGS__+0,(void*)0)
void _printlines(const char * first, ...)
{
const char * ptr;
va_list va;
va_start (va, first);
printf("---begin---\n");
for (ptr = first; ptr != NULL ; ptr = va_arg(va,char*) )
{
printf("%s\n", ptr);
}
printf("---end---\n");
va_end(va);
}
int main()
{
printlines(); // instead of: printlines(NULL);
printlines("hi");
printlines("how","are","you");
return 0;
}

Save time, enable all complier warnings.
warning: 'next' may be used uninitialized [-Wmaybe-uninitialized] } while(next); quickly gets to the key issue.
warning: control reaches end of non-void function [-Wreturn-type] in 2 places.
This is faster than posting on stack overflow.

The "rubbish" comes from not initialized object next. Another one next defined in the loop stops to exist when you exit the loop.
Removing strange functions and cleaning some mess you can get right.
int printline(const char* str){
printf("%s",str);
}
#define printlines(...) printlinesfunc(__VA_ARGS__,(char*)0)
int printlinesfunc(const char* first, ...){
if(first)
{
va_list ptr;
va_start(ptr,first);
char* next;
printline(first);
while((next = va_arg(ptr, char *)))
printline(next);
va_end(ptr);
}
}
int main(){
printlines("hi" , "\n");
printlines("how"," are"," you", "\n");
printlines("help", "\n");
printlines("something", " is", " wrong", "\n");
}

I highly recommend that you avoid variadic functions and use pointer arrays and variadic macros instead (with a terminator object).
Your function would have looked like this when using this approach:
void printline(const char *str) { printf("%s\n", str); }
int printlines(char **lines) {
if (!lines)
return -1;
while (*lines)
printline(*(lines++));
return 0;
}
#define printlines(...) printlines((char *[]){__VA_ARGS__, NULL})
Not only are variadic functions sometimes difficult to code, but the ABI for variadic functions is problematic to the point that different languages might treat it differently and C bindings between different languages might break your code.
Besides, when using this approach, things can become much more fun and interesting as well, allowing for easy type detection and multi-type arguments... this code from the facil.io CSTL library provides a good example for what I mean.
The function accepts an array of structs:
/** An information type for reporting the string's state. */
typedef struct fio_str_info_s {
/** The string's length, if any. */
size_t len;
/** The string's buffer (pointer to first byte) or NULL on error. */
char *buf;
/** The buffer's capacity. Zero (0) indicates the buffer is read-only. */
size_t capa;
} fio_str_info_s;
/** memory reallocation callback. */
typedef int (*fio_string_realloc_fn)(fio_str_info_s *dest, size_t len);
/** !!!Argument type used by fio_string_write2!!! */
typedef struct {
size_t klass; /* type detection */
union {. /* supported types */
struct {
size_t len;
const char *buf;
} str;
double f;
int64_t i;
uint64_t u;
} info;
} fio_string_write_s;
int fio_string_write2(fio_str_info_s *restrict dest,
fio_string_realloc_fn reallocate, /* nullable */
const fio_string_write_s srcs[]);
Then a macro makes sure the array's last element is a terminator element:
/* Helper macro for fio_string_write2 */
#define fio_string_write2(dest, reallocate, ...) \
fio_string_write2((dest), \
(reallocate), \
(fio_string_write_s[]){__VA_ARGS__, {0}})
Additional helper macros were provided to make the fio_string_write_s structs easier to construct. i.e.:
/** A macro to add a String with known length to `fio_string_write2`. */
#define FIO_STRING_WRITE_STR2(str_, len_) \
((fio_string_write_s){.klass = 1, .info.str = {.len = (len_), .buf = (str_)}})
/** A macro to add a signed number to `fio_string_write2`. */
#define FIO_STRING_WRITE_NUM(num) \
((fio_string_write_s){.klass = 2, .info.i = (int64_t)(num)})
And the function used the terminator element to detect the number of arguments received by the macro:
int fio_string_write2 (fio_str_info_s *restrict dest,
fio_string_realloc_fn reallocate, /* nullable */
const fio_string_write_s srcs[]) {
int r = 0;
const fio_string_write_s *pos = srcs;
size_t len = 0;
while (pos->klass) {
switch (pos->klass) { /* ... */ }
/* ... counts total length */
++pos;
}
/* ... allocates memory, if required and possible ... */
pos = srcs;
while (pos->klass) {
switch (pos->klass) { /* ... */ }
/* ... prints data to string ... */
++pos;
}
/* ... house-keeping + return error value ... */
}
Example use (from the source code comments):
fio_str_info_s str = {0};
fio_string_write2(&str, my_reallocate,
FIO_STRING_WRITE_STR1("The answer is: "),
FIO_STRING_WRITE_NUM(42),
FIO_STRING_WRITE_STR2("(0x", 3),
FIO_STRING_WRITE_HEX(42),
FIO_STRING_WRITE_STR2(")", 1));
This both simplifies the code and circumvents a lot of the issues with variadic functions. This also allows C bindings from other languages to work better and the struct array to be constructed in a way that is more idiomatic for the specific target.

Related

Can you pass a string by value in C?

Strings are interpreted as pointers when you pass them to a function in C, for example func(str) will pass a pointer to the block of memory named str. So by default, strings are already pointers when passing them into a function. Is there a way to pass a string by value where you don't have to end up manipulating the contents of str ?
First, what is a string? It's an array of characters and then a \0. Just to make sure we all agree on that.
So the question is, can you pass an array of characters by value? And the answer is yes, but only if it's inside a struct or union:
struct String40Chars {
char str[41];
};
void f(struct String40Chars s) {
// s was passed by value - including s.str
}
However, you can't pass an array directly due to a historical quirk:
void f(char str[41]) {
// you would think this would work,
// but actually, the compiler converts it to void f(char *str)
}
That is unfortunate for people who really do want to pass arrays by value.
Luckily you usually don't need to. In every case I can think of, a pointer is good enough, and faster as well since it doesn't need to copy the array. Maybe you can elaborate on why you need to pass an array by value.
P.S. You talk about "strings being pointers" but that is nonsense. Strings are arrays. You can have pointers to arrays but the pointer is not the array.
The only way to achieve value semantics with string arguments in C is to have a struct or union that wraps the string.
Something like:
#define STR_BY_VALUE_MAX_LENGTH 128
typedef struct StrByValue_
{
char str[STR_BY_VALUE_MAX_LENGTH];
} StrByValue;
void f(StrByValue strByVal)
{
// Changes to strByVal will not affect the caller
}
Of course the caller will have to initialize the StrByValue object by copying the string (e.g. using strcpy) into the str field. You can also add a len field to the StrByValue struct if you don't want to use zero terminated strings.
Other answer are storing the string on the stack memory, which cannot be resized after initialization, which I don't think is a long runner. According to me string should be stored on heap memory so that we can expand it later, think something of a data structure that expands when something is appended at the end.
To implement this nicely and with some sugar syntax, we need to create 3 functions namely init_string(), append_string() and free_string().
init_string(): initialize the pointer and assign len = INIT_STR_LEN
append_string(): can be used for concatenation of two strings
free_string(): free the heap allocated ptr, hence no memory leaks
Code:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct
{
char *ptr;
size_t len;
} string;
string init_string(const char *s)
{
string x;
x.len = strlen(s);
x.ptr = malloc(x.len + 1);
if (!x.ptr)
{
fprintf(stderr, "err: memory can't be allocated\n");
exit(EXIT_FAILURE);
}
memcpy(x.ptr, s, x.len + 1);
return x;
}
void append_string(string *s, const char *str)
{
if (!str || !s)
{
fprintf(stderr, "err: null was passed\n");
exit(EXIT_FAILURE);
}
size_t str_l = strlen(str);
s->ptr = realloc(s->ptr, s->len + str_l + 1);
if (!s->ptr)
{
fprintf(stderr, "err: memory can't be allocated\n");
exit(EXIT_FAILURE);
}
memcpy(s->ptr + s->len, str, str_l + 1);
s->len += str_l;
}
void free_string(string *s)
{
if (!s)
{
fprintf(stderr, "err: null was passed\n");
exit(EXIT_FAILURE);
}
free(s->ptr);
s->len = 0;
}
void foo(string a) // got the value of a
{
// cannot modified permanently
}
void bar(string *a) // got the address of a
{
// can be modified permanently
}
int main(void)
{
string str = init_string("Hello world, I'm a c-string.");
puts(str.ptr);
append_string(&str, "stackoverflow");
puts(str.ptr);
free_string(&str);
return EXIT_SUCCESS;
}

Variable not updating even with volatile, no threads

I have the following printf statement which prints the output of a function:
printf("\nStart-Time %s,End-Time %s",
get_time(myfields[i].start_mn),
get_time(myfields[i].end_mn));
In the statement the get_time function is called twice with different arguments. However, even though the function returns different strings on the two calls, the printf only prints the return from the first call for the second call also. I have tried the volatile keyword for the function return variable and the function itself, but the output is still the same.
But if I split the printf statement into two printf stataments to print the values separately,they are printing different values which is expected.
So can anyone point to me what is happening here and what is the correct way of doing this?
[Update from comment:]
get_time is returning a global char array. char *get_time(int tval) converts time in minutes to hour.
So can anyone point to me what is happening here and what is the correct way of doing this?
Each call to get_time() returns the same address to the same buffer, which are then passed to printf(), which finds in this very one buffer what had been put in last, with "last" in terms of "last in time".
To get around this create temporary buffers to pass to printf():
char * p1 = strdup(get_time(...));
char * p2 = strdup(get_time(...));
printf("\nStart-Time %s,End-Time %s", p1, p2);
/* Free the temporary buffers. */
free(p1);
free(p2);
A more simple approach would do if you know the maximum size of what get_time() returns in advance:
#define GET_TIME_LEN_MAX 32
...
char b1[GET_TIME_LEN_MAX];
char b2[GET_TIME_LEN_MAX];
strcpy(b1, get_time(...));
strcpy(b2, get_time(...));
printf("\nStart-Time %s,End-Time %s", b1, b2);
The safe version of the latter example:
#define GET_TIME_LEN_MAX 32
...
/* Define buffers and initialise them to ALL zeros. */
char b1[GET_TIME_LEN_MAX] = "";
char b2[GET_TIME_LEN_MAX] = "";
strncpy(b1, get_time(...), GET_TIME_LEN_MAX - 1);
strncpy(b2, get_time(...), GET_TIME_LEN_MAX - 1);
printf("\nStart-Time %s,End-Time %s", b1, b2);
As strdup() isn't Standard C, for completeness find a home grown implementation below:
#include <stdlib.h> /* for malloc() */
#include <string.h> /* for strcpy() */
char * strdup(const char * s)
{
char * p = NULL;
if (NULL != s)
{
p = malloc(strlen(s) + 1);
if (NULL != p)
{
strcpy(p, s);
}
}
return p;
}

Call back programming w.r.t 'c' only, why we should use?

The point being saying w.r.t c only, as I am more comfortable in C.
I am not expecting a example which says this is how it works ... What I am expecting is why should we use the Call back function or some say it as function pointer.
I followed many blog and stack-overflow also, but not satisfied with any of those answers.
Let's say ( I am suggesting one scenario here, like sorting thing) we should use the call back thing, where a method/function will take more time for processing.
Let's say a process is there with one thread only, and the program is doing a sorting, which will take huge time ( let's assume > 1 min ). According to huge no of bloggers here we should use the function pointer. But how it would be useful ?
Any how we are having only one Program Counter and we will get some amount of time to process this process from CPU, then how it would be useful ?
If you think some other example is there to explain the function pointer concept please provide the example.
I saw some body suggesting like, if you will use function pointer, then the result u can collect later, but this sounds really awkward ! how is this even if possible ? How can u collect something from a function after returning from there ? the function would have been destroyed right !!!
In real time people use this for any change in events, so that they can get notification...( just adding a point )
I have seen some good programmer using this function pointer, I am dying to know why would I use this , surely there is something I am missing here...
Please reply, thanks in Advance.
Since there was still a bit of uncertianty in your last comment, perhaps a concrete example illustrating the points would help. Let's start with a simple example that takes a string as user input from the command line (you could prompt the user for input as well). Now let's say we want to give the user to option to tell us how they want to store the input. For purpose of this example, lets say the options are (1) to store the string normally, such that it prints on one line horizonally, (2) store the reverse of the string which will also print on one line, (3) store the string with newlines after each character so it prints vertically, and (4) store the string in reverse with embedded newlines.
In a normal approach to this problem, you would probably code a switch statement or a series of else if statements and pass the string to 4 different routines to handle the different cases. Function pointers allow you to approach the problem a little differently. Rather than 4 different input routines to handle each case, why not 1 input routine that takes a function pointer as it's argument and changes the way it handles the string based on the function passed as an argument. The input routine could be as simple as making a copy of the string (to prevent modifying argv[1], etc...) and then passing the string as an argument to a function represented by the pointer:
/* make copy of original string, pass to callback function */
char *input (char *d, char *s, char *(*cbf)(char *))
{
strcpy (d, s);
return (*cbf) (d);
}
The input function above takes as arguments the destination string d, the source string s and then a pointer to a funciton cbf (short for callback function). Let's look at the function pointer syntax quickly and digest what it tells us:
char *(*cbf)(char *)
| | \
return | argument
type | list
|
function pointer
name/label
Above, the function pointer named cbf has a return type of char *, and takes a single argument of type char *. (note: only the type is specified in the funciton pointer argument list, not both the type and argument name -- e.g. no char *str, just char *) Now that may seem like a lot to type each time you want to pass a function of that type as an argument or use it in an assignment. It is. There is an easy solution to reduce the typing required. You can use a typedef for the function pointer similar to how you use a typedef with a struct, etc. Creating a typedef of type cbf is equally easy:
typedef char *(*cbf)(char *);
The funciton pointer typedef above creates a type cbf that can be used in place of char *(*cbf)(char *) wherever the function pointer type is needed. When a typedef is used, you are relieved from specifying the return type and the argument list as well as not having to put the function pointer inside parenthesis. This reduces the original function declaration to:
char *input (char *d, char *s, cbf fname)
{
strcpy (d, s);
return fname (d);
}
Making use of a typedef for a function not only simplifies passing the functions as argument, but also simplifies creating arrays of funciton pointers as well. An array of funtion pointers can be used to simplify selecting and passing any one of a given number of functions, as needed. For our input function we create an array of function pointers each pointing to a different function that can be used to put the input string in the desired format. For example, let's say our 4 functions described above have declaration like this:
/* input processing callback functions */
char *hfwd (char *s);
char *hrev (char *s);
char *vfwd (char *s);
char *vrev (char *s);
note: each of the functions match our pointer definition of type char * and accept a single argument of type char *. Using our cbf typedef, we can easily create an array of function pointers called fnames as follows:
cbf fnames[] = { &hfwd, &hrev, &vfwd, &vrev };
The fnames array can then be used like any other array to select any one of our functions by array index. (e.g. fnames[0] is our function hfwd) This now gives us the ability to take a second input from our user, a number, to select the format for our input string. This provides the ability to use any one of our callback function by simply giving the array index for the desired function as the second argument on the command line. For example any one of the functions can be designated by calling out program with:
./progname my_string 1 /* to process the input with the hrev */
Now granted this example does not do much more than reformat a string, but from the standpoint of function pointer syntax, collecting function pointers in an array, and passing a function pointer as an argument to extend the capabilities of your code, it covers a great deal. Take a look at the following example, and let me know if you have any questions. (recall, the full function pointer syntax, in the absence of a typedef, is also included, but commented so you can compare/contrast typedef use)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXS 128
/* typedef for function pointer */
typedef char *(*cbf)(char *);
/* simple string reverse function */
char *strrevstr (char *str);
/* input processing callback functions */
char *hfwd (char *s);
char *hrev (char *s);
char *vfwd (char *s);
char *vrev (char *s);
/* input function, pointer to function will determine behavior */
// char *input (char *d, char *s, char *(*cbf)(char *));
char *input (char *d, char *s, cbf fn);
int main (int argc, char **argv) {
if (argc < 3 ) {
fprintf (stderr, "error: insufficient input, usage: %s string int\n", argv[0]);
return 1;
}
int idx = atoi(argv[2]);
if (idx > 3 || idx < 0) {
fprintf (stderr, "error: invalid input -- out of range, (0 !< %d !< 3)\n", idx);
return 1;
}
cbf fnames[] = { &hfwd, &hrev, &vfwd, &vrev };
// char *(*fnames[])(char *) = { &hfwd, &hrev, &vfwd, &vrev };
char string[MAXS] = {0};
input (string, argv[1], fnames[idx]);
printf ("\nProcessed input ('%s' '%s'):\n\n%s\n\n", argv[1], argv[2], string);
return 0;
}
/* strrevstr - reverse string, original is not preserved. */
char *strrevstr (char *str)
{
if (!str) {
printf ("%s() error: invalid string\n", __func__);
return NULL;
}
char *begin = str;
char *end = str + strlen (str) - 1;
char tmp;
while (end > begin)
{
tmp = *end;
*end-- = *begin;
*begin++ = tmp;
}
return str;
}
/* string unchanged - print horizontal */
char *hfwd (char *s)
{ return s; }
/* string reversed - print horizontal */
char *hrev (char *s)
{ return strrevstr (s); }
/* string unchanged - print vertical */
char *vfwd (char *s)
{
char *p = s;
static char buf[MAXS] = {0};
char *b = buf;
while (*p)
{
*b++ = *p++;
*b++ = '\n';
}
*b = 0;
b = buf;
while (*b)
*s++ = *b++;
*b = 0;
return buf;
}
/* string reversed - print vertical */
char *vrev (char *s)
{
char *p = strrevstr (s);
static char buf[MAXS] = {0};
char *b = buf;
while (*p)
{
*b++ = *p++;
*b++ = '\n';
}
*b = 0;
b = buf;
while (*b)
*s++ = *b++;
*b = 0;
return buf;
}
/* make copy of original string, pass to callback function */
char *input (char *d, char *s, cbf fn)
// char *input (char *d, char *s, char *(*cbf)(char *))
{
strcpy (d, s);
return fn (d);
// return (*cbf) (d);
}
Output
$ ( for i in {0..3}; do ./bin/fnc_pointer my_string $i; done )
Processed input ('my_string' '0'):
my_string
Processed input ('my_string' '1'):
gnirts_ym
Processed input ('my_string' '2'):
m
y
_
s
t
r
i
n
g
Processed input ('my_string' '3'):
g
n
i
r
t
s
_
y
m

How to correctly prototype C functions

I'm learning the concept of prototyping in C, however I'm struggling with the correct syntax. I'm writing a function to strip all non-alphbetic characters from a c-string
#include <stdio.h>
#include <string.h>
char[30] clean(char[30] );
int main()
{
char word[30] = "hello";
char cleanWord[30];
cleanWord = clean(word);
return 0;
}
char[30] clean(char word[30])
{
char cleanWord[30];
int i;
for(i=0;i<strlen(word);i++)
if ( isalpha(word[i]) )
cleanWord[i]=word[i];
cleanWord[i]='\0';
return cleanWord;
}
How do I correctly prototype the function? What are the other syntax errors that are preventing my program from compiling?
Your problem is not with function prototyping (aka forward declaration). You just can't return an array from a function in C. Nor can you assign to an array variable. You need to make a couple of changes to get things working. One option:
change char cleanWord[30] in main to be char * cleanWord.
change the signature of clean to char *clean(char word[30])
use malloc to allocate a destnation buffer inside clean
return a pointer to that new buffer
free the buffer in main
And another:
change the signature of clean to void clean(char word[30], char cleanWord[30])
operate on the passed-in pointer rather than a local array in clean
change the call in main to be clean(word, cleanWord).
As Carl Norum said, you can't return an array. Instead, what you tend to do is supply the output:
void clean( const char word[30], char cleanWord[30] )
{
}
And you should remove the locally-scoped array from that function.
You will find that the function does not work correctly, because you only have one iterator i. That means if a character is not an alpha, you will skip over a position in the output array. You will need a second iterator that is incremented only when you add a character to cleanWord.
A couple of notes (was a bit late with writing up an answer, seems I've been beaten to them by the others )
C cannot return local (stack) objects, if you want to return an array from a function you have to malloc it
Even if you declare an array argument as (char arr[30]), (char* arr) is just as valid as arrays decay to pointers when passed as arguments to functions. Also, you won't be able to get the size correctly of such arrays by using sizeof. Even though it's 30, on my machine it returns 4 for word in clean, which is the size of the pointer for it.
You are missing an include, isalpha is part of ctype.h
I've updated your code, hopefully I've guessed your intentions correctly:
#include <stdlib.h> /* for malloc and free */
#include <string.h> /* for strlen */
#include <ctype.h> /* for isalpha */
#include <stdio.h> /* for printf */
/* Function declaration */
char* clean(char word[30]);
/* your 'main()' would now look like this: */
int main()
{
char word[30] = "hel1lo1";
char* cleanWord;
cleanWord = clean(word);
printf("%s\n", cleanWord);
free(cleanWord);
return 0;
}
/* Function definition */
char* clean(char word[30])
{
char* cleanWord = malloc(30); /* allocating dynamically an array of 30 chars,
* no need to use sizeof here as char is
* guaranteed to be 1 by the standard
*/
unsigned int i, j = 0; /* let's fix the problem with non-alpha chars already pointed out */
for (i = 0; i < (strlen(word)); i++)
if (isalpha(word[i]))
cleanWord[j++] = word[i];
cleanWord[j] = '\0';
return cleanWord;
/* return a pointer to the malloc`ed array, don't forget to free it after you're done with it */
}

printf() seems to be destroying my data

I'm writing an nginx module in C and am having some super bizarre results. I've extracted a function from my module to test its output as well as the relevant nginx type/macro definitions.
I'm building a struct in my build_key_hash_pair function, then doing a printf() on the contents in main. When I printf the data inside the inner function, main's output is valid. When I remove the printf inside the inner function, main prints an empty string. This is confusing because after the function call to build_key_hash_pair I am not operating on the data except to display it. Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct ngx_str_t {
size_t len;
char *data;
} ngx_str_t;
typedef uintptr_t ngx_uint_t;
typedef struct key_hash_pair {
ngx_uint_t hash;
ngx_str_t key;
} key_hash_pair;
#define ngx_string(str) { sizeof(str) - 1, (char *) str }
#define ngx_str_set(str, text) \
(str)->len = sizeof(text) - 1; (str)->data = (char *) text
#define ngx_hash(key, c) ((ngx_uint_t) key * 31 + c)
#define ngx_str_null(str) (str)->len = 0; (str)->data = NULL
void build_key_hash_pair(key_hash_pair *h, ngx_str_t api_key, ngx_str_t ip);
int main (int argc, char const *argv[])
{
ngx_str_t api_key = ngx_string("86f7e437faa5a7fce15d1ddcb9eaeaea377667b8");
ngx_str_t ip = ngx_string("123.123.123.123");
key_hash_pair *pair;
pair = malloc(sizeof(key_hash_pair));
build_key_hash_pair(pair, api_key, ip);
printf("api_key = %s\n", api_key.data);
printf("ip = %s\n", ip.data);
printf("pair->key = %s\n", pair->key.data);
printf("pair->hash = %u\n", (unsigned int)pair->hash);
return 0;
}
void build_key_hash_pair(key_hash_pair *h, ngx_str_t api_key, ngx_str_t ip)
{
ngx_str_null(&h->key);
char str[56];
memset(str, 0, sizeof(str));
strcat(str, api_key.data);
strcat(str, ip.data);
ngx_str_set(&h->key, str);
ngx_uint_t i;
for (i = 0; i < 56; i++) {
h->hash = ngx_hash(&h->hash, h->key.data[i]);
}
}
Here is the output when I do a printf("hello") inside the build_key_hash_pair function:
helloapi_key = 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8
ip = 123.123.123.123
pair->key = 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8123.123.123.123
pair->hash = 32509824
And here is the (bizarre) output when I do NOT printf inside build_key_hash_pair:
api_key = 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8
ip = 123.123.123.123
pair->key =
pair->hash = 32509824
As you can see, pair->key has no data. In gdb, if I breakpoint right after the call in main to build_key_hash_pair, pair->key contains the appropriate data. But after the first call to printf, it is blanked out. The memory address stays the same, but the data is just gone. Can anyone tell me what in the world I'm doing wrong?
This line is a problem:
ngx_str_set(&h->key, str);
Here str is a local variable, and you are putting a pointer to it inside h->key, which will be returned to the caller. After build_key_hash_pair returns, the pointer will no longer be valid. When you didn't call any other function, the pointer happened to still point to the same value, but this is not something you can rely on. The call to printf overwrote that part of the stack.
What you need is either to dynamically allocate the string with malloc or strdup, or put an array inside the key_hash_pair struct to hold the key (possible if the key is always the same size).
build_key_hash_pair uses stack-based array str to populate the data field in the key of h. When you exit from the function, that pointer is no longer valid since str goes out of scope.
Your results could be anything from apparently correct operation to a program failure. printf in the function will work, but definitely not if called afterwards. ngx_str_set needs to allocate memory and copy the text string into it (to be freed later of course).
I would replace all those macros with functions or inline code, personally.
The problem is in the build_key_hash_pair function, specifically with the stack variable char str[56]; which is assigned to the key_hash_pair via the macro ngx_str_set.
Since the stack frame containing char str[56]; disappears when the function returns, all bets are off for the value of of the pair's data once the function ends.

Resources