6

strtok() を使用して簡単な URL パーサーを作成しました。ここにコードがあります

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    char *protocol;
    char *host;
    int port;
    char *path;
} aUrl;


void parse_url(char *url, aUrl *ret) {

    printf("Parsing %s\n", url);
    char *tmp = (char *)_strdup(url);
    //char *protocol, *host, *port, *path;
    int len = 0;

    // protocol agora eh por exemplo http: ou https:
    ret->protocol = (char *) strtok(tmp, "/");
    len = strlen(ret->protocol) + 2;

    ret->host = (char *) strtok(NULL, "/");


    len += strlen(ret->host);

    //printf("char at %d => %c", len, url[len]);

    ret->path = (char *)_strdup(&url[len]);

    ret->path = (char *) strtok(ret->path, "#");

    ret->protocol = (char *) strtok(ret->protocol, ":");

    // host agora é por exemplo address.com:8080
    //tmp = (char *)_strdup(host);
    //strtok(tmp, ":");
    ret->host = (char *) strtok(ret->host, ":");
    tmp = (char *) strtok(NULL, ":");

    if(tmp == NULL) {
        if(strcmp(ret->protocol, "http") == 0) {
            ret->port = 80;
        } else if(strcmp(ret->protocol, "https") == 0) {
            ret->port = 443;
        }
    } else {
        ret->port = atoi(tmp);
    }


    //host = (char *) strtok(NULL, "/");




}

/*
 * 
 */
int main(int argc, char** argv) {
    printf("hello moto\n");

    aUrl myUrl;
    parse_url("http://teste.com/Teste/asdf#coisa", &myUrl);


    printf("protocol is %s\nhost is %s\nport is %d\npath is %s\n", myUrl.protocol, myUrl.host, myUrl.port, myUrl.path);

    return (EXIT_SUCCESS);
}

ご覧のとおり、私は strtok() をよく使うので、URL を「スライス」できます。http や https 以外の URL をサポートする必要はないので、この方法ですべての問題が解決します。私の懸念は(これは組み込みデバイスで実行されています)-メモリを無駄にしていますか?のようなものを書くとき

ret->protocol = (char *) strtok(tmp, "/");

そして、後で呼び出します

ret->protocol = (char *) strtok(ret->protocol, ":");

最初に保持されたポインタ ret->protocol はメモリに残りますか? 最初の呼び出しを tmp ポインターに設定し、ret->protocol を文字列の右側の部分に指定して strtok を呼び出し (2 番目の呼び出し)、次に free(tmp) を呼び出す必要があるのではないかと考えました。

strtok を使用する最良の方法は何ですか?

4

4 に答える 4

23

質問に直接答えるために、 strtok は、入力として指定した文字列内の場所へのポインターのみを返します。新しいメモリを割り当てないため、提供されるポインターのいずれかで free を呼び出す必要はありません。お返しに。

価値があるのは、「strchr」と「strstr」を調べることもできます。これらは、文字列内の単一の文字またはシーケンスを非破壊的に検索する方法です。

また、ここではメモリ割り当てに問題があることに注意してください。strdup() を使用して解析関数内に新しい文字列を割り当ててから、そのメモリ ブロックのフラグメントを「ret」のフィールドに割り当てています。したがって、呼び出し元は strdup された文字列を解放する責任がありますが、その文字列を ret 内で暗黙的に返すだけなので、呼び出し元は free に渡すポインターを魔法のように知る必要があります。(おそらく ret->protocol ですが、入力がどのように見えるかによってはそうではないかもしれません。)

于 2009-09-29T22:58:18.340 に答える
5

strtokは、指定された文字をNULLに置き換えて、その場で文字列を変更します。Cの文字列はNULLで終了しているため、元の文字列がまだ存在し、同じ量のメモリを占有している場合でも(ただし、文字がNULLに置き換えられている場合)、元のポインタが短い文字列を指しているように見えます。文字列の終わりには、double-NULLが含まれていると思います。

簡単な答えは次のとおりです。文字列バッファの先頭へのポインタを保持し、解析時に文字列への「現在の」ポインタである別のポインタを用意します。strtokを使用するか、他の方法で文字列を反復処理する場合は、「現在の」ポインタを更新しますが、開始ポインタはそのままにしておきます。終了したら、開始ポインタをfree()します。メモリリークはありません。

于 2009-09-30T13:25:59.193 に答える
3

strtok の最初のパラメーターとして NULL を使用して、文字列の解析を続行できることをご存知ですか?

最初の呼び出し:

char* token = strtok(string, delimiters);

それで:

token = strtok(NULL, other_delimiters);

これにより、コードを簡素化できます。

int parse_url(char *url, aUrl *ret)
{
//get protocol
char* token = strtok(url, "/");
if( token == NULL )
   return -1;
strcpy(ret->protocol, token);
strcat(ret->protocol, "//");

// skip next '/'
token = strtok(NULL, "/");
if( token == NULL )
   return -1;

//get host
token = strtok(NULL, "/");
if( token == NULL )
   return -1;
strcpy(ret->host, token);

// get path
token = strtok(NULL, "#");
if( token == NULL )
   return -1;
strcpy(ret->path, token);

// ...

return 0;
}

解析が成功したかどうかを知るための戻り値があったことがわかります。

于 2009-09-29T23:17:23.863 に答える
1

コードを共有していただきありがとうございます。valgrind内で実行し、strdup関数によって生成された2つのメモリリークを修正しました。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char *protocol;
    char *host;
    int port;
    char *path;
} URL;

void parse_url(char *url, URL *ret) {
    char *tmp = (char *) strdup(url);
    int len = 0;

    ret->protocol = (char *) strtok(tmp, "/");
    len = strlen(ret->protocol) + 2;
    ret->host = (char *) strtok(NULL, "/");
    len += strlen(ret->host);
    ret->path = (char *) strdup(&url[len]);
    ret->path = (char *) strtok(ret->path, "#");
    ret->protocol = (char *) strtok(ret->protocol, ":");
    ret->host = (char *) strtok(ret->host, ":");
    tmp = (char *) strtok(NULL, ":");

    if (tmp == NULL) {
        if (strcmp(ret->protocol, "http") == 0) {
            ret->port = 80;
        } else if (strcmp(ret->protocol, "https") == 0) {
            ret->port = 443;
        }
    } else {
        ret->port = atoi(tmp);
    }

}

void free_url(URL *url) {
    free(url->path);
    free(url->protocol);
}

int main(int argc, char** argv) {
    URL url;
    parse_url("http://example.com:3000/Teste/asdf#coisa", &url);
    printf("protocol: %s\nhost: %s\nport: %d\npath: %s\n", url.protocol, url.host, url.port, url.path);
    free_url(&url);

    return (EXIT_SUCCESS);
}
于 2010-11-03T11:05:51.867 に答える