Find substrings in string I'm self-learning C now, and I want to find out if my code is good and clean or kinda messy.
For this exercise, the program tells if the text which was inserted has the keyword "asd", without using functions from string.h. If so, it returns how many times it was found.
it's not a big program, but I want to make sure it's clean and good so I don't pick up any bad practices.

#include "stdafx.h"
#include &lt;stdio.h&gt;


char findInStr(char *str, char *find) {
    unsigned char howMany = 0, strCounter = 0, findCounter = 0;
    do {
        if (findCounter == 3)
            howMany++;
        if (str[strCounter] == find[findCounter])
            findCounter++;
        else
            findCounter = 0;
        strCounter++;
    } while (str[strCounter-1] != '\0');
    return howMany;
}


int main() {
    char str[60];
    char find[5] = "asd";
    char found;


    printf("enter text: ");
    gets_s(str, 60);

    found = findInStr(str, find);
    if (found)
        printf("found!!! %d\n times", found);
    else
        printf("not found\n");

    return 0;
}
The problem statement

There's an ambiguity in the requirements, because you don't specify how to handle overlapping substrings.  For example, should findInStr("ababa", "aba") return 1, or 2?  Either of those may be useful, but be clear which interpretation is needed.  This code appears to count non-overlapping substrings.

Non-standard header

Despite its name, stdafx.h is not a standard header, so you can't use it in portable code.  Thankfully this program can easily become standard C, if you replace gets_s() with the equivalent fgets() call:

if (!fgets(str, sizeof str, stdin)) {
    perror("gets");
    return 1;
}


Note: this is not quite equivalent (thanks to Martin R for the comment), as get_s() discards the final newline; that won't make a difference unless we specifically look for a string ending in newline (which could now match once, rather than never).

Naming

I like the variable names you've used - I found it easy to understand your algorithm with the clear names.  (They do need to be a more suitable type - see Martin's answer for a very clear explanation).

I think they would be even clearer if you give them a line each for their declarations.

Bug (or undocumented constraint)

This test assumes that find will always be three characters long:

    if (findCounter == 3)
        howMany++;


We can make more versatile by counting until we see the NUL character at the end of find, like this:

    if (find[findCounter] == '\0') {
        howMany++;
        findCounter = 0;
    }


Bug

If a character is repeated in find, we don't start looking again in the right place.  We can test this with findInStr("ababaca", "abac") - this doesn't find the string because when we reach the second b, that doesn't match c and we start again from there (instead of going back to the start of the potential match).

The fix for this to subtract findCounter from strCounter before we reset it to zero, so we resume searching from the right place:

        if (str[strCounter] == find[findCounter]) {
            findCounter++;
        } else {
            strCounter -= findCounter;
            findCounter = 0;
        }




Modified version

Keeping your algorithm, but incorporating the fixes from this and other answers, I get:

#include &lt;stdio.h&gt;

/* Count the number of non-overlapping occurrences of find in str.
 * Returns zero if either string is empty.
 */
size_t findInStr(const char *str, const char *find)
{
    size_t howMany = 0;
    size_t strCounter = 0;
    size_t findCounter = 0;

    do {
        if (find[findCounter] == '\0') {
            ++howMany;
            findCounter = 0;
        }
        if (str[strCounter] == find[findCounter]) {
            ++findCounter;
        } else {
            strCounter -= findCounter;
            findCounter = 0;
        }
    } while (str[strCounter++] != '\0');

    return howMany;
}


I changed the test program to accept command-line arguments; this makes for easier testing.  I hope that this is educational reading (note that I check the arguments before attempting to use them).

int main(int argc, char **argv)
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s haystack needle\n", argv[0]);
        return 1;
    }

    const char *const haystack = argv[1];
    const char *const needle = argv[2];

    const size_t found = findInStr(haystack, needle);

    switch (found) {
    case 0:
        printf("'%s' was not found in '%s'.\n", needle, haystack);
        break;
    case 1:
        printf("'%s' was found once in '%s'.\n", needle, haystack);
        break;
    default:
        printf("'%s' was found %zd times in '%s'.\n", needle, found, haystack);
        break;
    }

    return 0;
}




Pointer version

This is a bit more advanced, and harder to get right, but I'll show you what this looks like when converted to use pointers rather than indexes.  A good optimizing compiler will likely generate the same code for both, but you'll want to be able to study and work with pointer code when you come across it.

size_t findInStr(const char *str, const char *const find)
{
    size_t howMany = 0;

    for (const char *find_pos = find;  *str;  ++str) {
        if (*str == *find_pos) {
            /* matched this char */
            if (!*++find_pos) {
                /* reached the end of find; count it and reset */
                ++howMany;
                find_pos = find;
            }
        } else {
            /* not matched - back up and continue */
            str -= find_pos - find;
            find_pos = find;
        }
    }

    return howMany;
}


Here, I've also used simply *str rather than the longer, but equivalent *str != '\0'.



Self-test program

An alternative main() can run a test suite, which can be useful in to avoid introducing bugs as you modify the code.  Here's what I accumulated when writing the above:

/* return failure count (0 or 1) */
static int test(const char *haystack, const char *const needle, size_t expected)
{
    const size_t actual = findInStr(haystack, needle);
    if (actual == expected) {
        return 0;
    }
    /* incorrect result */
    fprintf(stderr,
            "FAIL: findInStr(\"%s\", \"%s\") returned %zd instead of %zd\n",
            haystack, needle, actual, expected);
    return 1;
}

int main(void)
{
    return
        + test("abc", "", 0)
        + test("abc", "d", 0)
        + test("abc", "cd", 0)
        + test("abc", "ac", 0)
        + test("abc", "ab", 1)
        + test("ab", "abc", 0)
        + test("abab", "ab", 2)
        + test("ababa", "aba", 1)
        + test("ababac", "bac", 1)
        ;
}


You might choose to use a preprocessor #ifdef/#else to be able to compile the test or interactive version from one source, or you might write two programs sharing a single findInStr() implementation.