Add two digit strings and return the result as string
Always Initialize Variables

There is one possible bug in the code, the variables d1 and d2 assume that both num1 and num2 have values, this may not be true. The C programming language does not initialize variables by default like some other languages, especially local variables. All of the variables should be initialized before the loop.

This also creates multiple paths through the code if either num1 or num2 do not have a value and error checking is added.

char * addStrings(char * num1, char * num2){
    int len1 = strlen(num1);
    int len2 = strlen(num2);

    if (!len1 &amp;&amp; !len2)
    {
        fprintf(stderr, "Neither string has an integer value.");
        return "0";
    }
    else
    {
        if (!len1)
        {
            return num2;
        }
        if (!len2)
        {
            return num1;
        }
    }

    ...

    return rp;
}


Complexity

With the added error check the function addStrings() is too complex (does too much). As programs grow in size the use of main() should be limited to calling functions that parse the command line, calling functions that set up for processing, calling functions that execute the desired function of the program, and calling functions to clean up after the main portion of the program.

There is also a programming principle called the Single Responsibility Principle that applies here. The Single Responsibility Principle states:


  that every module, class, or function should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by that module, class or function.


Probably the best thing to do here is to break addStrings() into 2 functions, one that does the error checking, and one that calculates the value of the string. The addStrings() function  should do the error checking itself, and then call a function to calculate the value of the string.

Missing Headers

In addition to the missing header files noted by vnp an include of the header file string.h is necessary to allow strlen() to compile, at least on my computer with my C compiler (Windows 10, Visual Studio 2019).

DRY Code

There is a programming principle called the Don't Repeat Yourself Principle sometimes referred to as DRY code. If you find yourself repeating the same code multiple times it is better to encapsulate it in a function. If it is possible to loop through the code that can reduce repetition as well.

This code is repetitive and the manipulation of the pointers and counters isn't necessary at this point.

    if (len1) {
        d1 = (*p1)-zero;
        p1--;
        len1--;
    } 
    if (len2) {
        d2 = (*p2)-zero;
        p2--;
        len2--;
    }


It might be better to just check if pN has a value and use what it points to if it does, change the pointers and the counters at the end of the loop. 

    if (p1) {
        d1 = (*p1) - zero;
    }

    if (p2) {
        d2 = (*p2) - zero;
    }


Scope of Variables

The integer variables digit, d1 and d2 are never used outside the loop so they can be declared within the loop itself. Only declare the variables when they are needed.

In Summation

As noted by vnp there is a memory leak.

An example of what how I would write the code:

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

static const unsigned char zero = '0';

char addCharacters(char *p1, char *p2, int *carry)
{
    int digit = 0;
    int d1 = 0;
    int d2 = 0;

    if (p1) {
        d1 = (*p1) - zero;
    }

    if (p2) {
        d2 = (*p2) - zero;
    }

    int tmp = d1 + d2 + *carry;
    digit = tmp % 10;
    *carry = tmp / 10;

    return digit + zero;
}

char* calculateStringValue(char *result, char num1[], int len1, char num2[], int len2, int result_len)
{
    char *rp = result + result_len;
    *rp = '\0';
    rp--;

    int carry = 0;
    char *p1 = num1 + len1-1;
    char *p2 = num2 + len2-1;

    while (len1 || len2)
    {
        *rp = addCharacters(p1, p2, &amp;carry);
        --len1;
        --len2;
        p1 = (len1 &gt; 0) ? --p1 : NULL;
        p2 = (len2 &gt; 0) ? --p2 : NULL;
        --rp;
    }

    if (carry) {
        *rp = carry + zero;
    } else {
        rp++;
    }

    return rp;
}

char * addStrings(char * num1, char * num2){
    int len1 = strlen(num1);
    int len2 = strlen(num2);

    if (!len1 &amp;&amp; !len2)
    {
        fprintf(stderr, "Neither string has an integer value.");
        return "0";
    }
    else
    {
        if (!len1)
        {
            return num2;
        }
        if (!len2)
        {
            return num1;
        }
    }

    int result_len = len1 &gt; len2? len1: len2;
    result_len += 1;    // allow for carry.

    char *result = malloc(result_len + 1);
    if (result == NULL)
    {
        fprintf(stderr, "malloc failed in addStrings\n");
        return NULL;
    }

    return calculateStringValue(result, num1, len1, num2, len2, result_len);
}

int main() {
    char num1[] = "2048";
    char num2[] = "9999";
    printf("AddStrings returns %s\n", addStrings(num1, num2));
    return 0;
}