Segmentation fault - sprintf

时间:2016-04-04 16:29:47

标签: c segmentation-fault

I am having troubles identifying why there is a segmentation fault happening in my program. I have done things like this before successfully, but this specific function is causing a segmentation fault.

My code is here:

void logErrorStatus(char* message)
{
    char* errorMessage = "test";

    sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);

    logMessage(errorMessage);
}


void logMessage(char* message)
{
    FILE* file = NULL;

    char buffer[SIZE];
    struct tm *sTm;

    time_t now = time(NULL);
    sTm = localtime(&now);

    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", sTm);

    file = fopen(LOGFILE, "a");

    if(file == NULL)
    {
        printf("Error opening log file.\n");
    }
    else
    {
        fprintf(file, "%s : %s\n", buffer, message);
    }

    if(fclose(file) != 0)
    {
        printf("Error closing log file.\n");
    }

}

The seg fault is happening on the sprintf() line in the logErrorStatus() function.

Any help would be great! Thanks!

6 个答案:

答案 0 :(得分:2)

Since errorMessage points to the memory allocated for the "test" string literal, the following line attempts to write to non-writable memory location:

sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);

You need to allocate errorMessage in writable memory to avoid undefined behavior:

char errorMessage[1000]; // 1000 is the max length of the buffer here.

This should get rid of the crash, but it is not ideal, because in extreme cases %ss may still overflow the buffer of 1000 chars. It is better to force truncation on both of them by limiting their size explicitly:

sprintf(errorMessage, "ERROR (%64s, %d) >> %900s", __FILE__, __LINE__, message);

答案 1 :(得分:2)

You are attempting to overwrite a constant string literal, which is probably defined in a read-only memory section, with a new value. You should instead dynamically allocate and free your memory.

As an example:

void logErrorStatus(char *message)
{
    //ALLOCATE 1024 IN CASE YOU CHANGE THE STRING LITERAL BELOW, AND ADD LENGTH OF MESSAGE
    char *errorMessage = malloc(1024 + strlen(message));

    if (errorMessage != NULL)
    {
        sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);

        logMessage(errorMessage);

        free(errorMessage);
    }
}

or allocate memory on the stack:

void logErrorStatus(char *message)
{
    //ALLOCATE 1024 AS MAX LENGTH OF OUTPUT
    char errorMessage[1024];

    sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);

    logMessage(errorMessage);
}

答案 2 :(得分:1)

"test" is a string literal of type char [5] and is allocated in constant memory. Although it is not defined as const by the standard you can not write into it.

Unfortunately nothing stops you from assigning it to char * making impression it is writeable :-(

This is what the standard says about it:

6.4.5 "String Literals - Semantics":

5 In translation phase 7, a byte or code of value zero is appended to each multibyte character sequence that results from a string literal or literals.66) The multibyte character sequence is then used to initialize an array of static storage duration and length just sufficient to contain the sequence. For character string literals, the array elements have type char, and are initialized with the individual bytes of the multibyte character sequence;

6 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.

GCC utilizes this by storing them into R/O memory and this is what documentations says about it:

Modification of string literals is not allowed by the GNU C compiler, because literals are placed in read-only storage.

All the above means you should replace:

char* errorMessage = "test";

sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);

with something like:

char errorMessage[BUFSZ];
snprintf(errorMessage, BUFSZ, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);

please note the use of snprintf which is the safe way to printf into array. It won't overrun the buffer and damage unrelated regions of memory.

答案 3 :(得分:1)

This code in your function logErrorStatus

char* errorMessage = "test";
sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);
logMessage(errorMessage);

first defines a pointer to a string literal, and then attempts to overwrite its data. There are two problems with that:

  • The string literal "test" is not large enough to contain the new data.
  • String literals are read-only, so attempting to write to them will cause problems.

I suggest

char errorMessage[256];
snprintf(errorMessage, sizeof errormessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);
logMessage(errorMessage);

答案 4 :(得分:0)

This code is incorrect:

char *errorMessage = "test";
sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);

Here you make errorMessage point to read-only 5 bytes large static storage (which is occupied by "test" + implicit '\n').

Any decent compiler with enabled warnings will yell at you when you try to assign an address of read-only storage to pointer to non-const char.

Thus, you need a mutable storage. You can do this:

char* errorMessage = malloc(256);
sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);
logMessage(errorMessage);
free(errorMessage);

Or this:

char errorMessage[256];
sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);
logMessage(errorMessage);

In these examples you may replace 256 with a size of a largest string you want to be able to handle.

答案 5 :(得分:0)

variable errorMessage is fixed to the the affected value length, in your case "test" so 4, for example cannot add content to errorMessage like this errorMessage[5]= 'o' because it is hard coded string, a solution is to allocate the required space to hold the formatted message (ERROR (%s, %d) >> %s, of course with the length of %s,%d and %s):

EDIT:( as said by @owacoder, we have to free the unused memory free(errorMessage))

void logErrorStatus(char* message)
{
char* errorMessage = (char*)malloc(sizeof(char)*512);

sprintf(errorMessage, "ERROR (%s, %d) >> %s", __FILE__, __LINE__, message);

logMessage(errorMessage);

free(errorMessage);
}

if you do not want to wast memory (but you'll lose in performance) you can allocate just the required space like this:

 char* errorMessage = (char*)malloc(sizeof(char)+(14+strlen(__FILE__)+5+strlen(message)+5);
//14 for message characters => "ERROR (, ) >> "
//5 for the variable __line__ size, it is an int so it's max value is 32767 in standard implementations
//1 for the \0 charachter