3

次の C コードでセグメンテーション違反が発生します。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT  6667
#define MAXDATASIZE 1024

int bot_connect(char *hostname);

int bot_connect(char *hostname) {

  int sockfd, numbytes, s;
  char buf[MAXDATASIZE];
  struct addrinfo hints, *servinfo, *p;
  int rv;
  char m[1024];
  char *message;
  char *nick = "Goo";
  char *ident = "Goo";
  char *realname = "Goo";

  memset(&hints,0,sizeof hints);
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;

  rv = getaddrinfo(hostname, PORT, &hints, &servinfo);

  if (rv != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
    return 1;
  }

  for (p = servinfo; p != NULL; p = p->ai_next) {
    sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if (sockfd == -1) {
      perror("Client: socket");
      continue;
    }

    if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
      close(sockfd);
      perror("Client: connect");
      continue;
    }

    break;
  }

  if (p == NULL) {
    fprintf(stderr, "Client: failed to connect \n");
    return 2;
  }

  freeaddrinfo(servinfo);

  strcat(m, "NICK ");
  strcat(m, nick);
  message = m;
  s = send(sockfd, message, strlen(message), 0);

  strcat(m, "USER ");
  strcat(m, ident);
  strcat(m, " * * :");
  strcat(m, realname);
  message = m;
  s = send(sockfd, message, strlen(message), 0);

  message = "JOIN #C&T";
  s = send(sockfd, message, strlen(message), 0);

  close(sockfd);
}

読み取り専用メモリの変更など、許可されていないメモリで何かをしようとするとセグメンテーション違反が発生することは知っていますが、私の知る限り、このプログラムはそれを行いません。セグメンテーション違反がどこから来ているのか、誰にも手がかりがありますか?

4

6 に答える 6

10

strcat( m, "NICK" );m を初期化する前に呼び出しています。strcat の前に、 または を試すm[0] = '\0';memset( m, 0, sizeof( m ) );、最初のものを に変更strcatしますstrcpy

また、NICK 行をソケットに送信した後、strcatもう一度呼び出します。これにより、USER 行が NICK 行に追加されます。繰り返しますが、おそらく最初のものを次のように変更する必要がありstrcatますstrcpy

于 2008-11-21T13:32:01.930 に答える
5

Graeme は "what"で正しいので、よくわからない場合に備えて、ここに "why" を示します。C では、文字列はヌル文字 '\0' で終わる一連の文字として定義されます。文字列に終了文字がある理由は、コードが文字列の論理的な終了位置を把握できるようにするためです。次のような文字列を宣言したとしても:

 char m[1024];

C では、与えられた m のコードに 1024 バイトが割り当てられていることを把握する方法はありません。ましてや、意味のあるバイトの末尾を区切るヌル文字を使用せずに、それらのバイトのうち何バイトが意味を持つかを把握することはできません。

strcat は 2 つの C 文字列で機能する関数であるため、両方の引数が、意味のある入力の終わりを示すヌル バイトがあるという仕様に準拠していることを期待しています。m が初期化されていない場合、その内容はランダムであるため、null バイトはどこにでもある可能性があり、null バイトがまったくない可能性もあります。連結するために、strcat は最初の引数の文字を調べて、文字列の終わりを示すヌル バイトを見つけようとします。たまたま、初期化されていない配列に null バイトが存在しない場合は、文字列の末尾を越えて、他の変数や内部スタック情報などに対応する可能性のある任意のメモリ位置を検索し続けます。

strcat が最終的に null バイトを見つけると、その位置から始まる 2 番目の引数の内容を喜んで書き込みます。その位置が m の末尾を超えている場合は、他の変数または他の情報を上書きします。他の変数に割り当てられたメモリアドレスの場合、問題は静かで、見つけるのが非常に難しい場合があります。幸いなことに、あなたの場合、それは書き込みを行うべきではない場所だったので、プログラムは明らかなエラーでクラッシュしました。

これにより、実際には strcat の代わりに strncat を使用する必要があるという注意事項も表示されます。strncat を使用すると、作成する文字列の最大長を指定できます。strcat は、m に 1024 バイトが割り当てられていることを判断できないため、m を 1023 文字より長くする何かと連結しようとすると、同じメモリ上書きの問題が発生することを前に述べました。strncat は、結果の文字列の最大長を指定する 3 番目の引数を取り、この長さに達したために連結が切り捨てられたかどうかを示すリターン コードを持ちます。

于 2008-11-21T13:44:53.510 に答える
4

理由と内容に対する正しい答えはすでにここにあります。「セグメンテーション違反の場所...」に答えるには、デバッガーをお勧めします。デバッグ情報を使用してプログラムをコンパイルすると、それがどこで発生したかがわかります (実行させて、クラッシュしたときのスタック トレースを確認してください)。答えは、理由と内容にも答える可能性が最も高いです(この場合はすでに答えられています)

于 2008-11-21T14:09:27.830 に答える
2

コンパイラの警告レベルを上げます。

rv = getaddrinfo(hostname, PORT, &hints, &servinfo);

getaddrinfo の 2 番目のパラメータはconst char *int ではなく です。PORT を NULL に変更し、addrinfo 構造体のポート番号を変更してからconnect.

于 2008-11-21T13:44:44.587 に答える
1

valgrindを使用します。Valgrind は、コード内のリソース リークやその他の一般的なヒューマン エラー タイプの誤動作を見つけるのに非常に優れています。

于 2008-11-21T13:39:43.753 に答える
0

配列には使用前のmガベージが含まれています。次のように動作します。

strcpy(m, "NICK ");
strcat(m, nick);
message = m;
s = send(sockfd, message, strlen(message), 0);

strcpy(m, "USER ");
strcat(m, ident);
strcat(m, " * * :");
strcat(m, realname);
message = m;
s = send(sockfd, message, strlen(message), 0);

また、次のように動作します。

sprintf(m, "NICK %s", nick);
message = m;
s = send(sockfd, message, strlen(message), 0);

sprintf(m, "USER %s * * :%s", ident, realname);
s = send(sockfd, message, strlen(message), 0);
于 2013-03-13T21:37:09.600 に答える