-3

I'm trying to make a function that concatenates a, b, and c in a single variable called result.

#include <stdio.h>

const char* concat(char* a, char* b, char* c) {

  char *result;    // Concatenated variable (A + B)
  int i;           // Index of A, B and C
  int ir;          // Index of result

  // result = A
  for(i=0; a[i] != '\0'; i++) {
    ir = i;
    result[ir] = a[i];
  } // → for

  // result = A + B
  ir++;
  for(i = 0; b[i] != '\0'; i++) {
    if (i == 0)
      ir = ir + i;
    result[ir] = b[i];
  } // → for

  // result = A + B + C
  ir++;
  for(i = 0; c[i] != '\0'; i++) {
    ir = ir + i;
    result[ir] = c[i];
  } // → for

  return result;
} // → concatenate()

But when I compile and run with the following block it results in Segmentation fault (core dumped) error. I've already searched about it (What is a segmentation fault?) but the insight didn't come. What am I doing wrong?

int main() {

  char* a = "1234567";
  char* b = "abcdefg";
  char* c = "_______";


  printf("%s", concat(a, b, c));

}

ERROR:

Segmentation fault (core dumped)

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 3
    `char *result; result[ir] = a[i]` That isn't going to work. `result` is an unintialised pointer. Dereferencing such a pointer is Undefined Behaviour. Allocate dynamic memory to return or have the caller pass in a buffer. – kaylum Nov 14 '20 at 00:44
  • 2
    You have declared `result` as a pointer but to where does it point? As it stands, it's completely uninitialized. You need to allocate some memory and point to that. – Adrian Mole Nov 14 '20 at 00:45
  • 2
    You don't allocate any memory for `*result`. Where do you expect `result[ir]` to come from? You're poking around in unallocated memory. – Ken White Nov 14 '20 at 00:45
  • Your copying code is weird too. If you resolve the problem with `result` not pointing anywhere, you still have problems. The first loop is OK, though it's not the obvious way of writing it. The second loop is broken, and the third is too. You should be using `for (i = 0; a[i] != '\0'; i++) result[ir++] = a[i];` for each of the three loops, with no modification to `ir` between loops, and (obviously) changing `a` to `b` and `c` in the second and third loops. You'd also need `result[it] = '\0';` after the third loop to null terminate the string. – Jonathan Leffler Nov 14 '20 at 00:59
  • Are you aware of dynamic memory allocation and `malloc()` and `free()` etc? If you know about that, you can solve the problem in one way; if you don't, you have to do things a different way. – Jonathan Leffler Nov 14 '20 at 01:02

2 Answers2

3

The code shown is trying to store data into an undefined buffer (result), and that's not not kosher. SOMEBODY has to allocate the space, either your function, or the caller by passing in a bounded output buffer.

Using the allocate-memory mechanism, you have to get the full size of all the strings in advance so you can allocate the full chunk at once. After allocating the full chunk, copy in the strings one at a time.

char *concat3(const char *a, const char *b, const char *c)
{
    // get the required size in advance
    size_t n = 0;

    for (const char *ap = a; *ap; ap++) n++;
    for (const char *bp = b; *bp; bp++) n++;
    for (const char *cp = c; *cp; cp++) n++;

    char *result = malloc(n + 1);

    char *op = result;

    while (*op = *a++) op++;
    while (*op = *b++) op++;
    while (*op = *c++) op++;

    return result;
}

Also note that the parameters are const char * rather than char * to make sure everybody knows that concat3 - I changed the name - will not write to these input buffers.

However, the return value cannot be const because you have to free it, which I hope you remember to do.

EDIT I added a different version that writes into a bounded output buffer so there's no memory allocation required in the function because the caller is responsible for it:

char *concat(char *obuf, size_t osize, const char *a, const char *b, const char *c)
{
    char *obuf_save = obuf;

    const char *obuf_max = obuf + osize - 1;

    while (obuf < obuf_max  &&  (*obuf = *a++)) obuf++;
    while (obuf < obuf_max  &&  (*obuf = *b++)) obuf++;
    while (obuf < obuf_max  &&  (*obuf = *c++)) obuf++;

    *obuf = 0;

    return obuf_save;
}

The key part is passing the output buffer and size, and it promises not to overwrite the end of the buffer.

Key downside: you can't tell if it truncated or not. Depends on your application if this matters.

Steve Friedl
  • 3,929
  • 1
  • 23
  • 30
  • Not bad! But: (1) Why not `size_t n = 1;` to begin with and no more `n + 1` in the `malloc`. (2) Where did you add the `NUL` terminator to `result`? – Adrian Mole Nov 14 '20 at 01:12
  • I thought about `n=1` but decided to add it explicitly in the `malloc()` to ensure it was obvious. And the final NUL byte is copied when the final `while (*op = *c++)` test fails due to copying the final NUL byte of `c` – Steve Friedl Nov 14 '20 at 01:14
  • Good point on the NUL character! – Adrian Mole Nov 14 '20 at 01:16
  • 1
    You could have the second function return `obuf - obufsave` as a `size_t` value, which would tell you if truncation occurred (if the return value is greater than or equal to `osize`, truncation occurred). The length should exclude the terminal null, just like `strlen()` excludes it. – Jonathan Leffler Nov 14 '20 at 03:36
  • Yah, that would work too. – Steve Friedl Nov 14 '20 at 03:57
1

You're never initializing result, so it will have a random value, so you've got UB (undefined behavior), which can result in a segfault.

You have to do a malloc for result. And, it needs to have enough space to hold the length of all the concatenated strings.

Here's some updated code. Note that I did not thoroughly check your for loops, but, at first glance they seem to be okay.

But, you have to add an EOS at the end of result because the for loops do not do that.

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

size_t
mystrlen(const char *buf)
{
    const char *bp;

    for (bp = buf;  *bp != 0;  ++bp);

    return bp - buf;
}

#if 0
const char *
concat(char *a, char *b, char *c)
#else
char *
concat(const char *a, const char *b, const char *c)
#endif
{
    char *result;                       // Concatenated variable (A + B)
    int i;                              // Index of A, B and C
    int ir;                             // Index of result

// NOTE/BUG: result is _never_ given a value
#if 1
    size_t totlen = 0;

    // we need to determine the total length of the result string
    totlen += mystrlen(a);
    totlen += mystrlen(b);
    totlen += mystrlen(c);

    result = malloc(totlen + 1);
    *result = 0;
#endif

    // result = A
    for (i = 0; a[i] != '\0'; i++) {
        ir = i;
        result[ir] = a[i];
    }

    // result = A + B
    ir++;
    for (i = 0; b[i] != '\0'; i++) {
        if (i == 0)
            ir = ir + i;
        result[ir] = b[i];
    }

    // result = A + B + C
    ir++;
    for (i = 0; c[i] != '\0'; i++) {
        ir = ir + i;
        result[ir] = c[i];
    }

    result[totlen] = 0;

    return result;
}

Edit: This was my original post. But, as others have pointed out, it was not the real issue.

So, disregard this section altogether!

I'm leaving it in because several responders here also misread the intent of your function at first glance. So, you may wish to add more comments to clarify the use of arguments and return value.

When you do:

char* a = "1234567";

You are creating a pointer that points to a string literal. On most/many systems, the literal will be placed in read only memory.

So, you will segfault on a protection exception [because you're trying to write to read only memory].

Also, the target buffer needs to be large enough to contain the size of all the concatenated strings. As you have it, a is too short/small.

You could try:

char a[100] = "1234567";

This creates a writable buffer of length 100 that is initialized with the string. But, you'll still have extra space to accomodate the concatenated strings [up to a total length of 100].

Craig Estey
  • 30,627
  • 4
  • 24
  • 48