今天在看TCP/IP 协议时看到一段代码,于是就拷贝下来自己编译运行一下,结果出现一个segmentation fault,这是一个奇怪的现象,奇怪在哪里呢?请往下看。
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd,
(struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd);
}
}
这是一个服务端代码,编译没有问题,这段代码主要工作就是:接收到客户端的字符后,转换成大写再发给客户端。客户端的代码也帖一下:
/* client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;
if (argc != 2) {
fputs("usage: ./client message\n", stderr);
exit(1);
}
str = argv[1];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
write(sockfd, str, strlen(str));
n = read(sockfd, buf, MAXLINE);
printf("Response %u charactor from server: %s\n", n, buf);
close(sockfd);
return 0;
}
Makefile
COMPILE_DIR = compile
BIN_DIR = bin
# 编译arm版本
#CROSS = arm-himix200-linux-
CC = $(CROSS)gcc
CFLAGS = -W -g
LIB = -lpthread
# SRCS = $(wildcard *.cpp)
# SRCS = $(shell ls -t | grep "\.c$$" | head -1)
SRCS = server.c client.c
OBJS = $(patsubst %.c, $(COMPILE_DIR)/%.o, $(SRCS))
DEP = $(patsubst %.c, $(COMPILE_DIR)/%.c.d, $(SRCS))
$(shell if [ ! -d $(COMPILE_DIR) ]; then mkdir $(COMPILE_DIR); fi)
$(shell if [ ! -d $(BIN_DIR) ]; then mkdir $(BIN_DIR); fi)
TARGET_SERVER=$(BIN_DIR)/server
TARGET_CLIENT=$(BIN_DIR)/client
all: $(TARGET_SERVER) $(TARGET_CLIENT)
$(TARGET_SERVER): server.c
$(CC) $(CFLAGS) $< -o $@ $(LIB)
$(TARGET_CLIENT): client.c
$(CC) $(CFLAGS) $< -o $@ $(LIB)
$(COMPILE_DIR)/%.o: %.c $(COMPILE_DIR)/%.c.d
$(CC) $(CFLAGS) -c $< -o $@
$(COMPILE_DIR)/%.c.d: %.c
$(CC) $(CFLAGS) -MM -E -c $< -o $@
@sed 's/.*\.o/$(subst /,\/,$(dir $@))&/g' $@ > $@.tmp
@mv $@.tmp $@
.PHONY: clean
clean:
rm -rf $(COMPILE_DIR) $(BIN_DIR)
用 gdb 跟踪调试,segmentation fault 出现在库函数 printf() 里:
看这参数也没问题啊,而且我还 man 了一下 inet_ntop 的用法,如下:
除非 inet_ntop 返回 NULL,用 %s 打印的话可能会有问题,所以在gdb里我执行了一下 inet_ntop 函数,如:
这里不明白为什么返回值是 int 类型呢?而 man 出来的inet_ntop 函数是返回 const char * 类型的啊。而且当我把代码改成这样时,把 inet_ntop 提出来放到 if 里:
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd,
(struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
if(inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)) != NULL) {
printf("received from %s at PORT %d\n", str, ntohs(cliaddr.sin_port));
}
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd);
}
得到了一条编译警告:
是不是很奇怪?这里警告我比较了指针类型和int类型,难道这个函数原型用的是另外什么版本的吗?然后我发现,代码里没有包含头文件:<arpa/inet.h>,man 的时候看到的。于是我把这个头文件加上
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
再编译就没问题了,难道不包含声明函数原型的头文件,编译器就默认其返回值是 int 吗?就算没有包含原型声明,但在运行时链接的动态库里的函数,应该也是没问题才对啊,那为什么不包含头文件运行起来会出现段错误呢?而加了头文件后,运行则是正常的: