If you like learning from examples, I prepared one. Let's say that we have the following single-linked list:

that is represented as follows (click to enlarge):

We want to delete the node with the value = 8.
Code
Here is the simple code that do this:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
struct node_t {
    int value;
    node_t *next;
};
node_t* create_list() {
    int test_values[] = { 28, 1, 8, 70, 56 };
    node_t *new_node, *head = NULL;
    int i;
    for (i = 0; i < 5; i++) {
        new_node = malloc(sizeof(struct node_t));
        assert(new_node);
        new_node->value = test_values[i];
        new_node->next = head;
        head = new_node;
    }
    return head;
}
void print_list(const node_t *head) {
    for (; head; head = head->next)
        printf("%d ", head->value);
    printf("\n");
}
void destroy_list(node_t **head) {
    node_t *next;
    while (*head) {
        next = (*head)->next;
        free(*head);
        *head = next;
    }
}
void remove_from_list(int val, node_t **head) {
    node_t *del, **p = head;
    while (*p && (**p).value != val)
        p = &(*p)->next;  // alternatively: p = &(**p).next
    if (p) {  // non-empty list and value was found
        del = *p;
        *p = del->next;
        del->next = NULL;  // not necessary in this case
        free(del);
    }
}
int main(int argc, char **argv) {
    node_t *head;
    head = create_list();
    print_list(head);
    remove_from_list(8, &head);
    print_list(head);
    destroy_list(&head);
    assert (head == NULL);
    return EXIT_SUCCESS;
}
If you compile and run this code you'll get:
56 70 8 1 28 
56 70 1 28
Explanation of the code
Let's create **p 'double' pointer to *head pointer:

Now let's analyze how void remove_from_list(int val, node_t **head) works. It iterates over the list pointed by head as long as *p && (**p).value != val.


In this example given list contains value that we want to delete (which is 8). After second iteration of the while (*p && (**p).value != val) loop (**p).value becomes 8, so we stop iterating.
Note that *p points to the variable node_t *next within node_t that is before the node_t that we want to delete (which is **p). This is crucial because it allows us to change the *next pointer of the node_t that is in front of the node_t that we want to delete, effectively removing it from the list.
Now let's assign the address of the element that we want to remove (del->value == 8) to the *del pointer.

We need to fix the *p pointer so that **p was pointing to the one element after *del element that we are going to delete:

In the code above we call free(del), thus it's not necessary to set del->next to NULL, but if we would like to return the pointer to the element 'detached' from the list instead of the completely removing it, we would set del->next = NULL:
