memset не может установить строку в ноль и переходит в ошибку сегментации

Я делаю упражнение о UDP-сокетах в C. Когда клиент отправляет конкретное сообщение (например, привет), сервер должен отправить "Приятно познакомиться". Если стандартный ответ не найден, сервер отправляет "Нет подходящего ответа". Моя проблема в том что memset не удается, если я пытаюсь вернуть ответ следующим образом:

return "No suitable reply";

и это не так, если я возвращаю ответ таким образом:

char* foo = malloc(sizeof(char*));
memset(foo, 0, strlen(ses));
memcpy(foo, "No suitable reply", 17);
return foo;

Я попытался найти решение этой проблемы в Google и нашел то и это, но они, похоже, не решают мою проблему (сначала я подумал, что memset не работает со строкой, объявленной как char string[] = "something" но во втором примере они используют memset для статической строки).

Вот весь код (мемсет, о котором я говорю, находится в конце):

   /*
    Alessandro Dussin 5AI
    2018-17-11
    Write a program to handle a single UDP "connection"
    */

//Standard libraries
#include <stdio.h>
#include <stdlib.h>

//Sockets libraries and connection ahndling
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>

//Read/write ops on file descriptors
#include <unistd.h>
//String ops
#include <string.h>

#include <assert.h>
void chopN(char *str, size_t n)
{
    assert(n != 0 && str != 0);
    size_t len = strlen(str);
    if (n > len)
        return;  // Or: n = len;
    memmove(str, str+n, len - n + 1);
}

//Required by the exercise. Given a certain word or phrase, reply with a specific string
char* switchreply(char* str){
    //Extracts the word or phrase (Basically removes the "/command " word)
    chopN(str, strlen("/stdreply "));
    int i = 0;
    for(; i < strlen(str); i++){
        if(str[i] == '\n'){
            str[i] = '\0';
            break;
        }
    }
    if(strcmp(str, "ciao") == 0){
        return "ciao anche a te!";
    }
    else if(strcmp(str, "I hate you") == 0){
        return "I hate you too!";
    }
    return "";
}


char* stdreply(char *str){

    char* tmp = malloc(sizeof(char)*128);
    int i = 0;
    //printf("Entered stdreply... str at the start of the func: %s\n", str);
    for(; i < strlen(str); i++){
        tmp[i] = str[i];
        //printf("tmp: %s\n", tmp); //DEBUG
        if(strcmp(tmp, "/echo ") == 0){ // if(strcmp() == 0) is necessary because
                                        //otherwise 0 would be interpreted as FALSE
            //printf("Echo detected\n"); //DEBUG
            chopN(str, strlen("/echo "));
            str[strlen(str)] = '\0';
            return str;
        }
        else if(strcmp(tmp, "/stdreply ") == 0){
            //printf("I got into the else if\n"); //DEBUG
            char* tmpreply = calloc(strlen(str), sizeof(char*));
            tmpreply = switchreply(str);
            //printf("tmpreply: %s\n", tmpreply);
            str = malloc(sizeof(char*)*strlen(tmpreply));
            memcpy(str, tmpreply, strlen(tmpreply));
            //str[strlen(str)] = '\0'; //DEBUG
            //printf("str: %s\n", str); //DEBUG
            return str;
        }
        else if(strcmp(tmp, "/TODO") == 0){
            char* ses = malloc(sizeof(char*));
            memset(ses, 0, strlen(ses));
            memcpy(ses, "work in progress", 17);
            return ses;
        }

    }
    return "No suitable reply";

    }

    int main(int argc, char **argv){

    if(argc < 2){
        printf("Usage: ./server port");
        exit(0);
    }

    int serverfd;
    serverfd = socket(AF_INET, SOCK_DGRAM, 0);

    struct sockaddr_in server;
    server.sin_port = htons(atoi(argv[1]));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;

    if(bind(serverfd, (struct sockaddr *)&server, sizeof(server)) < 0){
        perror("Bind() error: ");
        fflush(stderr);
    }

    //"UDP message receiver" variables declarations
    int bytes; //Reads how many bytes the funcion recvfrom has read
    struct sockaddr_in from;

    char* buffer = malloc(sizeof(char*)); //String to which save the client message
    memset(buffer, 0, strlen(buffer)); //and set it to zero

    socklen_t fromlen = sizeof(struct sockaddr_in);

    const char stdrep[] = "Message Received: "; //This string will always be
                                                //printed upon receiving a message
    char* reply = malloc(sizeof(char*)); //This is where the return value of
                                        //stdreply() will be stored
    memset(reply, 0, strlen(reply)); //and set it zero

    //This while will keep "listening" for udp messages
    while((bytes = recvfrom(serverfd, buffer, 1024, 0, (struct sockaddr *)&from, &fromlen)) > 0){
        //From teacher's example. Write to stdout
        write(1, stdrep, strlen(stdrep));
        write(1, buffer, bytes);
        //Detect a basically empty string (if the client has pressed only enter)
        if(buffer[0] == '\n'){
            bytes = sendto(serverfd, "You pressed only enter!\n", 18, 0, (struct sockaddr *)&from, fromlen);
        }

        //Act according to the client message
        reply = stdreply(buffer);
        bytes = sendto(serverfd, reply, strlen(reply), 0, (struct sockaddr *)&from, fromlen);
        if (bytes  < 0){
            perror("sendto: ");
            fflush(stderr);
        }
        memset(buffer, 0, 1024);
        memset(reply, 0, strlen(reply)); //The seg fault happens right here
        fflush(stdout);
    }
    return 0;
}

2 ответа

Решение

В коде, который вы публикуете, много проблем.

  1. Как уже отмечал @JonBolinger, sizeof(char*) возвращает размер в байтах указателя на символ. На платформах Intel это будет 4 или 8, в зависимости от того, используете ли вы 32-разрядную или 64-разрядную версию. (Таким образом, вы в конечном итоге выделяете буферы по 4 или 8 байт)

  2. Вы последовательно пытаетесь очистить свои динамически распределенные буферы с помощью memset(), malloc() вернет память, заполненную мусором, и вы укажете, сколько байтов очистить, используя strlen() на возвращенном буфере. strlen() будет сканировать буфер, пока не найдет первый символ 0 для вычисления длины строки. Поскольку буфер заполнен мусором, это может легко дать вам значение за пределами вашего блока памяти, и вы в конечном итоге повредите память.

  3. Каждый звонок malloc() должны соответствовать free() позвони, иначе у тебя утечка памяти. Это особенно важно, если ваша программа работает долго.

Когда вы работаете с временными локальными строками (строками, которые не возвращаются вызывающей стороне), очень распространенной практикой является использование локального массива char вместо malloc(). Таким образом, буфер выделяется в стеке и будет освобожден автоматически, когда ваша функция выйдет из области видимости. Просто обязательно используйте "безопасные" строковые функции, такие как strncpy() который будет принимать в качестве параметра длину буфера, чтобы избежать перезаписи.

void Example(char* anotherString ) {
    char tmpString[256];   // this will create a local buffer with capacity of 256 bytes
    strncpy(tmpString, anotherString, sizeof(tmpString));  // copy string, without risk of overflowing the buffer
}

Предупреждение: НИКОГДА не пытайтесь вернуть локальный временный буфер в результате, помните, что он больше не будет существовать при выходе из функции, и хотя возвращаемое значение может изначально иметь значимые результаты, они обязательно будут уничтожены, как только вы вызовете другую функцию. Вместо этого, другая распространенная практика, когда вам нужно возвращаемое значение строки вместо того, чтобы возвращать строку, выделенную с помощью malloc() -это должно быть выпущено с free() - вы передаете локальный буфер, который будет содержать результат как параметр, например:

void func1() {
    char result[256];
    func2(result, 256);
    // after calling, result will carry "a returned string"
}

void func2(char* result, size_t bufferLen) {
    strncpy(result, "a returned string", bufferLen);
}

Я думаю, что ваш код будет очень полезен, если вы сможете преобразовать его в этот стиль, где это применимо.

reply = stdreply(buffer);

Это не копирует строку. Указатель перезаписывается другим, теряя исходный указатель.

memset(reply, 0, strlen(reply));

Это очистит строку, если она будет выделена с помощью malloc. Если это постоянная строка типа "Нет подходящего ответа", то она может быть доступна только для чтения, поэтому она генерирует ошибку сегмента.

Другие вопросы по тегам