1

Hi I am at beginner level and I am using concept of variable argument functions to concatenate strings. Same function is called for different number of strings.

I am not able to calculate the length of the concatenated string which in turn means i am not allocating memory properly. My dear peers, please help!

/* Program to do string concatenation using the concept of variable arguments */

/********************************************************************************
*                               REQUIRED HEADER FILES
*********************************************************************************/

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>

/********************************************************************************
*                               REQUIRED MACROS DEFINED
*********************************************************************************/

#define ERROR_CHECKER(result)\
    if(result == FAILURE)\
    {\
        printf("\n CONCATENATION FAILED");\
    }
typedef enum {SUCCESS = 0, FAILURE = 1} return_type;

/********************************************************************************
*                               REQUIRED FUNCTION PROTOTYPES
*********************************************************************************/
return_type string_concat(char* string_pointer, ...);

/********************************************************************************
*
*    FUNCTION_NAME  :  STRING_CONCAT
*
*    DESCRIPTION    :  concatenates incoming strings and displays the result
*
*    RETURNS        :  SUCCESS OR FAILURE 
*
*********************************************************************************/
return_type string_concat(
    char* string_pointer, 
    ...)
{

/********************************************************************************
*                               REQUIRED DECLARATIONS
*********************************************************************************/
// 1. arg_list that will point to variable number of arguments 
va_list arg_list;

// 2. pointer to concatenated string 
char* concatenated_string;

// 3. character pointer to point to an individual element in the argument list
char* individual_string_pointer;

// 4. amount of memory required to be allocated 
int length;
/*********************************************************************************
*                               REQUIRED INITIALIZATIONS
*********************************************************************************/
va_start(arg_list, string_pointer);
concatenated_string = NULL;
individual_string_pointer = string_pointer;
length = 0;
/*********************************************************************************
*                               PERFORMING REQUIRED TASKS
**********************************************************************************/

// 1. calculate length till you reach quit
while(strcmp(individual_string_pointer,"quit") == 0)
{
    individual_string_pointer = va_arg(arg_list, char*);
    length = length + strlen(individual_string_pointer);
}

// individual_string_pointer reinitialized to be used for concatenation
individual_string_pointer = string_pointer;

printf("\nlength of concatenated string : %d", length);

// 2. allocate memory for the concatenated string
concatenated_string = (char*) malloc(sizeof(char) * length + 1);

// 3. use strncpy to copy first string and then use strncat to concatenate others

strncpy(concatenated_string, string_pointer, sizeof(*(string_pointer)));

while(strcmp(individual_string_pointer, "quit") == 0)
{
    individual_string_pointer = va_arg(arg_list, char*);
    strncat(concatenated_string, individual_string_pointer, sizeof(*(individual_string_pointer)));
}

printf("\n concatenated string : %s",concatenated_string);

va_end(arg_list);
return SUCCESS;
}

/********************************************************************************
*
*   FUNCTION_NAME   :   MAIN
*
*   DESCRIPTION     :   CALLS STRING_CONCAT FUNCTION
*
*   RETURNS         : SUCCESS 
*********************************************************************************/
int main(void)
{

/********************************************************************************
*                               REQUIRED DECLARATIONS
*********************************************************************************/
// 1. character array as the first argument
char string_one[5] = "hello" ;

// 2. variable to store result from the string_concat function.
int result;

/*********************************************************************************
*                               REQUIRED INITIALIZATIONS
**********************************************************************************/

result = 0;

/*********************************************************************************
*                               PERFORMING REQUIRED TASKS
**********************************************************************************/
// 1. call string_concat function with 2 arguments
   result = string_concat(string_one, "my", "name","is","amninder","quit");
    // handle error from string_concat
    ERROR_CHECKER(result);

// 2. call string_concat function with 3 arguments
   result = string_concat(string_one, "I", "Like","fruits","quit");
    // handle error from string_concat
    ERROR_CHECKER(result);  

// 3. call string_concat function with 4 arguments 
   result = string_concat(string_one, "awesome","quit");
    // handle error from string_concat
    ERROR_CHECKER(result);
/* doubt: do I need to send my first argument as same always " */
return SUCCESS;
}
Amninder Singh
  • 321
  • 1
  • 2
  • 20
  • able to calculate length by using != instead of == in the first while loop. But still the program is crashing after that.. Thank you – Amninder Singh Dec 31 '16 at 16:05
  • 1
    No need to use the `strn*()` versions. – alk Dec 31 '16 at 16:07
  • 4
    `string_one[5]` --> `string_one[6]` – BLUEPIXY Dec 31 '16 at 16:09
  • 4
    You can't use the same va_list twice: http://stackoverflow.com/questions/9309246/repeated-use-of-a-variadic-function-argument-doesnt-work – Unimportant Dec 31 '16 at 16:09
  • @BLUEPIXY .. obviously ... :D ... thanks by the way.. – Amninder Singh Dec 31 '16 at 17:12
  • @user1320881: the most upvoted answer to that question gives a wrong diagnosis of the trouble in the other question. The solution given using `va_copy()` works (though it requires C99), but isn't necessary. All that's required in the original code is to use `vprintf()` instead of `printf()`. – Jonathan Leffler Dec 31 '16 at 17:15
  • @user1320881 Thanks .. – Amninder Singh Dec 31 '16 at 17:25
  • 2
    `while(strcmp(individual_string_pointer,"quit") != 0)` is curious. Why `"quit"`? Suggest instead `const * const quit = NULL; ... string_concatresult = string_concat(string_one, "my", "name","is","amninder", quit); ... while(individual_string_pointer != quit)` – chux - Reinstate Monica Dec 31 '16 at 17:25
  • @chux thanks.. code looks more cleaner now..but I am passing quit as a string here so that is why using it like that – Amninder Singh Dec 31 '16 at 17:29
  • @Jonathan Leffler : Fair enough. Still, a problem here is trying to go trough the argument list twice without somehow resetting it. – Unimportant Dec 31 '16 at 17:31
  • @JonathanLeffler ... Happy new year :P ...umm... I am actually able to do it .. but have used two variables of type va_list, one being arg_list and other being arg_list2. arg_list helps to calculate the amount of memory required to allocate to the final string... arg_list2 is used to actually concatenate the string... – Amninder Singh Dec 31 '16 at 17:34
  • 2
    Changing the code significantly is not in keeping with SO standards. As code is now, the 1 answer does not make sense. Suggest reverting to previous edit. – chux - Reinstate Monica Dec 31 '16 at 17:42
  • 1
    @chux done :) ... – Amninder Singh Jan 01 '17 at 05:14

4 Answers4

2

Besides other issues: This sizeof(*(individual_string_pointer))); returns the size of what individual_string_pointer points to, namely a char, so it returns 1.

Either use strlen(ndividual_string_pointer) instead, or just switch to using strcat(), like this:

strcat(concatenated_string, individual_string_pointer)
alk
  • 69,737
  • 10
  • 105
  • 255
  • Now I am able to print my two strings- I have used above two things pointed out 1. using va_list twice won't work so created two arg_lists using va_list type namely arg_list and arg_list2 2. now using strlen(individual_string_pointer) instead of sizeof(). really thanks for this. – Amninder Singh Dec 31 '16 at 16:59
  • Still the program is crashing at the third string.. I am freeing memory every time still don't know why is it behaving like this ... Have updated my code above – Amninder Singh Dec 31 '16 at 17:00
  • phew.. finally done with it... actually there is problem in memory allocation here - while(strcmp(individual_string_pointer,"quit") != 0) { length = length + strlen(individual_string_pointer); /* line 1 */ individual_string_pointer = va_arg(arg_list, char*); /* line 2 */ } actually I was putting line 2 before line 1 ... Thank you everyone.. – Amninder Singh Dec 31 '16 at 17:11
  • 1
    @Amninder: note that the "can't use `va_list` twice" stuff is not entirely accurate (being polite about it). – Jonathan Leffler Dec 31 '16 at 17:27
  • @JonathanLeffler hmm.. I know it now.. used 2 va_list variables to do the tasks. just seeing others approach now.. – Amninder Singh Jan 01 '17 at 05:19
0

Your first problem is that you are not doing anything with the data. Your second is an annonying rule preventing iteration over argument lists twice.

Return the concatenated string as malloced memory, or 0 on fail

char *concat(const char *first, ...)
{
    char *answer;
    int lenght;
    va_list va;
    char *nest;
    int nextlen;

    length = strlen(first);
    answer = malloc(length + 1);
    if(!answer)
       goto out_of_memory;
    strcpy(first, answer);

    va_start(va, &first);
    do
    {
        next = va_arg(va, char *);
        if(!strcpy(nest, "quit))
          break;
        nextlen = strlen(next);
        temp = realloc(answer, length + nextlen+1);
        if(!temp)
           goto out_of_memory;
        answer = temp;
        strcpy(answer, next);
        length += nextlen;
    } while(1);
    va_end(va);

    return answer;
    out_of_memory:
      free(answer);
      return 0;
}
Malcolm McLean
  • 6,258
  • 1
  • 17
  • 18
  • @AmninderSingh Like all tools, he must be used when it's needed. Error management is one of them. http://stackoverflow.com/questions/245742/examples-of-good-gotos-in-c-or-c, http://stackoverflow.com/questions/24451/is-it-ever-advantageous-to-use-goto-in-a-language-that-supports-loops-and-func. – Stargateur Jan 01 '17 at 05:34
0

Rather than iterate the va_list twice, consider realloc() and append each sub-string. Additional ideas in comments.

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>

#define return_type int
#define FAILURE -1
#define SUCCESS 0

//                        use const 
return_type string_concat(const char* string_pointer, ...) {
  va_list arg_list;
  va_start(arg_list, string_pointer);

  char* concatenated_string = NULL;
  size_t length = 0;  // Use type size_t, not int
  const char* individual_string_pointer = string_pointer;

  while (strcmp(individual_string_pointer, "quit")) {
    // Find sub-string length _once_
    size_t individual_length = strlen(individual_string_pointer);
    size_t new_length = length + individual_length;
    char *new_ptr = realloc(concatenated_string, new_length + 1);
    if (new_ptr == NULL) {
      free(concatenated_string);  // do not forget to free old string
      printf("\n MALLOC FALIED");
      return FAILURE;
    }

    concatenated_string = new_ptr;

    // or use memcpy(concatenated_string + length, 
    //            individual_string_pointer, individual_length+1)
    strcpy(concatenated_string + length, individual_string_pointer);

    length = new_length;

    individual_string_pointer = va_arg(arg_list, const char*);
  }
  va_end(arg_list);

  // Add <> to detect leading/trailing white-space
  printf("Concatenated string : <%s>\n", concatenated_string);
  free(concatenated_string);
  return SUCCESS;
}

int main(void) {
  //        not [5], let compiler size it to include the null character
  char string_one[] = "hello";
  string_concat(string_one, "my", "name", "is", "amninder", "quit");
  string_concat(string_one, "I", "Like", "fruits", "quit");
  return 0;
}

Ouput

Concatenated string : <hellomynameisamninder>
Concatenated string : <helloILikefruits>
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 2
    I'd say iterating twice is much better than using realloc. – hyde Dec 31 '16 at 20:17
  • @hyde Just giving options as OP was having trouble with 2 passes thought `va_list`. Of course you could post your better idea too. – chux - Reinstate Monica Dec 31 '16 at 22:29
  • I think the same... using realloc() in a loop causes memory fragmentation,.. but really thanks for the insight.. – Amninder Singh Jan 01 '17 at 05:20
  • Yeah, it's fine to consider using `realloc`, but in this case result of that consideration should be "I can easily find out the total allocation needed, so I should do just one allocation and not use `realloc`." If `realloc` is used in a loop, it should probably be used with exponentially growing allocation size, to limit the number of maximum reallocations. – hyde Jan 01 '17 at 11:16
  • @hyde The other benefit to a single pass (with repeated `realloc()`) is not calling `strlen()` twice per sub-string. IOWs, the whole impact of various approaches needs profiling versus analyzing the parts. For performance, I would use a fixed size array of string pointer/length pairs to handle the 99% of calls will less than N parameters (say 16 or 32) - then allocate the buffer. Additional code would handle pathological numerous arguments. Agree using exponential growing allocation _is_ a good idea, – chux - Reinstate Monica Jan 01 '17 at 19:14
0

Your approach is not bad, but could be improved, "quit" is not very safe to mark the end of va_list, NULL is a better choice. Because the user can make a mistake in "quit" like "qui", "qit" or whatever. And how concat "quit"? if you use string_concat without knowing what is inside the function could stop early when one of the strings is "quit":

char *string1 = "foo";
char *string2 = "quit";
char *string3 = "bar";
string_concat(string1, string2, string3, "quit");

Only "foo" will be used.

You don't return the string generate so your function is not really useful.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>

#define END_STRING_CONCAT ((char const *)NULL)

char *string_concat(char const *first, ...);

char *string_concat(char const *first, ...) {
  va_list ap;
  va_start(ap, first);

  va_list cp_ap;
  va_copy(cp_ap, ap); // copy for future use

  size_t size = 1; // need the size of the future string
  for (char const *ptr = first; ptr != NULL; ptr = va_arg(ap, char const *)) {
    size += strlen(ptr);
  }
  va_end(ap);

  char *result = malloc(size);
  if (result == NULL) {
    va_end(cp_ap);
    return NULL;
  }

  size_t used = 0;
  for (char const *ptr = first; ptr != NULL; ptr = va_arg(cp_ap, char const *)) {
    size_t len = strlen(ptr);
    if (size < used || size - used < len) {
      free(result);
      va_end(cp_ap);
      return NULL;
    }
    memcpy(result + used, ptr, len); // use memcpy because it's faster in this case
    used += len;
  }
  va_end(cp_ap);
  if (size < used || size - used != 1) {
    free(result);
    return NULL;
  }
  result[used] = '\0'; // don't forget

  return result;
}

int main(void) {
  char hello[] = "hello, ";

  char *result1 = string_concat(hello, "my ", "name ", "is ", "amninder",
                                END_STRING_CONCAT);
  if (result1 != NULL) {
    printf("%s\n", result1);
    free(result1);
  }

  char *result2 = string_concat(hello, "I ", "Like ", "fruits", END_STRING_CONCAT);
  if (result2 != NULL) {
    printf("%s\n", result2);
    free(result2);
  }

  char *result3 = string_concat(hello, "awesome", END_STRING_CONCAT);
  if (result3 != NULL) {
    printf("%s\n", result3);
    free(result3);
  }

  return 0;
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • `NULL` is not a _portable_ better choice. `NULL` is not defined to be a pointer type, let aloone a `char *`. `NULL` could be a `int` like `0`. Passing `NULL` into `...` argument does not guarantee that `NULL` is converted to a _null pointer_. So `va_arg(ap, char *)` may act on an `int`, which may differ than a `char*` - resulting UB. What is needed is a constant that is _specified_ to be a `char *` as [commented](http://stackoverflow.com/questions/41409846/program-string-concatenation-using-concept-of-variable-argument-functions/41410668?noredirect=1#comment70023998_41409846). – chux - Reinstate Monica Dec 31 '16 at 22:36
  • @chux Are you sure? because C99 define `NULL` as “expands to an implementation-defined null pointer constant,”. [Can a conforming C implementation #define NULL to be something wacky](http://stackoverflow.com/questions/2599207/can-a-conforming-c-implementation-define-null-to-be-something-wacky), I don't understand to me `NULL` is a pointer to `void`. Whatever if `NULL` is not an `void` pointer. This is useless to cast it into `char *`, no? It's will produce other undefined behavior because only `void *` and `char *` can be cast into other type of pointer, no? – Stargateur Jan 01 '17 at 05:24
  • Thanks chux... but I'll use memcpy() from now on instead of strncpy() coz it's faster.. so thanks both of you... Such discussions improve my learning..:) Thanks once again.. – Amninder Singh Jan 01 '17 at 05:25
  • "to me `NULL` is a pointer to void" --> that is not specified by the C spec. `NULL` could simply expand to `0`, an `int`. "`NULL` expands to an implementation-defined null pointer constant." (§7.19 3). A _null pointer constant_ (there may be more than 1) is not necessarily a _pointer_, as strange at that sounds. "If a null pointer constant is converted to a pointer type, the resulting pointer, called a _null pointer_" §6.3.2.3 3. – chux - Reinstate Monica Jan 01 '17 at 07:08
  • "useless to cast it into char *" --> It Is useful. The value and type passed to `string_concat(..., (char *) NULL)` then matches the type extracted with `va_arg(cp_ap, char *)` and compares as hoped for with `ptr != NULL`. – chux - Reinstate Monica Jan 01 '17 at 07:09
  • The primary source of UB is that `NULL` may be an `int`, perhaps of different size than a pointer, and `va_arg(cp_ap, char *)` is UB with `string_concat(..., NULL)`. A secondary source of UB is that if `NULL` is a `void *`, then `va_arg(cp_ap, char *)` may be a problem. IOWs, if `va_arg(cp_ap, char *)` is used, the matching parameter should be a `char *`. `const char *`, etc., not some other pointer type nor an `int`. – chux - Reinstate Monica Jan 01 '17 at 07:13