简体   繁体   中英

Clear the last element from a linked list

I am working on a C program which has a linked list. I need to remove the last element from the linked list and it is mostly working except when it hits particular part of my code it then has a segmentation fault.

The code that I have is as follows:

int clearOutboundLegFromList(callLogSearchOutboundStruct ** outboundLeg, int dataCol, int rowTargets)
{
    //callLogSearchOutboundStruct *currentStruct = *outboundLeg;
    //callLogSearchOutboundStruct *temp;
    if (*outboundLeg == NULL)
    {
        return 0;
    }
    SL_DebugAll(DBG_ALWAYS, "DEBUG: Clearing outbound legs: DataCol: %i RowTargets: %i",
            dataCol, rowTargets);
    callLogSearchOutboundStruct *legToRemove = NULL;
    callLogSearchOutboundStruct *last = NULL;
    legToRemove = *outboundLeg;

    while (legToRemove->nextLeg != NULL)
    {
        last = legToRemove;
        legToRemove = legToRemove->nextLeg;
    }
    if (legToRemove->target != NULL)
    {
        free(legToRemove->target);
        legToRemove->target = NULL;
    }
    if (legToRemove->cleardownCause)
    {
        free(legToRemove->cleardownCause);
        legToRemove->cleardownCause = NULL;
    }

    free(legToRemove);

    if (last != NULL)
    {
        last->nextLeg = NULL;
    }
    legToRemove = NULL;
}

It crashes on the line of free(legToRemove->target); .

In the core dump I have the following:

Program terminated with signal 11, Segmentation fault.
#0  0x00b01336 in _int_free () from /lib/libc.so.6
Missing separate debuginfos, use: debuginfo-install cyrus-sasl-lib-2.1.23-13.el6_3.1.i686 glibc-2.12-1.132.el6_5.2.i686 keyutils-libs-1.4-4.el6.i686 krb5-libs-1.10.3-15.el6_5.1.i686 libcom_err-1.41.12-18.el6.i686 libcurl-7.19.7-37.el6_5.3.i686 libidn-1.18-2.el6.i686 libselinux-2.0.94-5.3.el6_4.1.i686 libssh2-1.4.2-1.el6.i686 mysql-libs-5.1.73-3.el6_5.i686 nspr-4.9.2-1.el6.i686 nss-3.14.0.0-12.el6.i686 nss-softokn-freebl-3.12.9-11.el6.i686 nss-util-3.14.0.0-2.el6.i686 openldap-2.4.23-31.el6.i686 openssl-1.0.1e-16.el6_5.14.i686 zlib-1.2.3-29.el6.i686
(gdb) bt
#0  0x00b01336 in _int_free () from /lib/libc.so.6
#1  0x0805cd0b in clearOutboundLegFromList (outboundLeg=0xb5de7984, dataCol=9, rowTargets=11) at performreport.c:6731
#2  0x08058f33 in processDrilldownData (reportParameterArray=..., csvFile=0x8e3fc78, HandleDB=0xbfca7a14, resultReport=0x8e457a8, 

If I print from the core dump legToRemove->target gdb outputs the following:

$1 = 0x99235d8 ""

Now that looks like its a properly allocated memory space, it just contains an empty string so I don't understand why this would cause a segfault.

You don't show how your struct looks like or how you add legs to your linked list, but you have an error in your removal function that occurs if you remove the last node: In that case, your list head should be set to NULL .

This special case is the reason to pass the list head as pointer to pointer to leg: The function must be able to update the head when the first node is removed. If you don't do that, the value of the head in the calling function will be the same and it will refer to memory that you have just free d. It is illegal to access such memory.

So, an updated version of your code could look like this:

void clearOutboundLegFromList(callLogSearchOutboundStruct **outboundLeg)
{
    callLogSearchOutboundStruct *last = NULL;
    legToRemove = *outboundLeg;

    if (legToRemove == NULL) return;

    while (legToRemove->nextLeg) {
        last = legToRemove;
        legToRemove = legToRemove->nextLeg;
    }

    free(legToRemove->target);
    free(legToRemove->cleardownCause);
    free(legToRemove);

    if (last) {
        last->nextLeg = NULL;
    } else {
        *outboundLeg = NULL;        
    }
}

You need the explicit assignment at the end, because once you have initialised legToRemove , you are operating only with that local pointer.

If you are feeling more confident with double indirections via pointers to pointers, you could iterate to the end without local variabes:

void clearOutboundLegFromList(callLogSearchOutboundStruct **outboundLeg)
{
    if (*outboundLeg == NULL) return;

    while (*outboundLeg) {
        outboundLeg = &(*outboundLeg)->nextLeg;
    }

    free((*outboundLeg)->target);
    free((*outboundLeg)->cleardownCause);
    free(*outboundLeg);

    *outboundLeg = NULL;        
}

This will update the head pointer automatically when the first element is removed. The idea here ist that outboundLeg points to the head node at the beginning and to the previous node's nextLeg pointer on subsequent iterations. The additional indirection via (*outboundLeg) is more or less the same as accessing a node via the nextLeg member, except for the first node, in which you access the pointer through the head node pointer.

(Distraction: Your code is overly cautious when freeing the member pointers. It is legal to free a null pointer; this doesn't do anything, but means that you don't have to check for NULL in client code. Such a check might still be good practice, because many functions won't take null pointers. Setting the member pointers to NULL is a good idea if these pointers were still around for some time. But you are going to free the containing struct anyway soon. Setting the pointers to NULL is a bit like cleaning the bathroom just before you tear down the house. Setting legToRemove to NULL at the end of the function doesn't do anything: The pointer will go out of scope anyway. That's just an aside and retionale for my shorter code. Your checks aren't wrong and it is better to be cautious.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM