调用isalpha导致分段错误

我有以下程序导致分段错误。

#include  #include  #include  int main(int argc, char *argv[]) { printf("TEST"); for (int k=0; k<(strlen(argv[1])); k++) { if (!isalpha(argv[1])) { printf("Enter only alphabets!"); return 1; } } return 0; } 

我已经发现正是这条线导致了这个问题

 if (!isalpha(argv[1])) { 

argv[1][k]替换argv[1] argv[1][k]解决了这个问题。

但是,我觉得很奇怪程序会导致分段错误,甚至没有打印TEST 。 我还期望isalpha函数错误地检查char*指针的低字节是否指向argv[1] ,但似乎并非如此。 我有代码来检查参数的数量,但为简洁起见,此处未显示。

这里发生了什么事?

一般来说,讨论为什么未定义的行为导致这个结果或另一个结果是毫无意义的。

但也许尝试理解为什么会发生某些事情并不会有害,即使它超出了规范。

isalpha实现使用一个简单的数组来查找所有可能的unsigned char值。 在这种情况下,作为参数传递的值将用作数组的索引。 虽然真实字符限制为8位,但整数不是。 该函数将int作为参数。 这是为了允许输入不符合unsigned char EOF

如果你将一个像0x7239482342这样的地址传递给你的函数,这远远超出了所述数组的结尾,并且当CPU试图读取带有该索引的条目时,它就会脱离世界的边缘。 ;)

使用这样的地址调用isalpha是编译器应该提出关于将指针转换为整数的警告的地方。 你可能忽略了……

可能包含检查有效参数的代码,但它也可能只依赖于用户不传递不会传递的内容。

  1. printf没有刷新
  2. 从指针到整数的隐式转换应该至少生成约束违规的编译时诊断,产生的数字超出了isalpha的范围。 isalpha被实现为查找表意味着您的代码访问了超出范围的表,因此未定义的行为。
  3. 为什么你没有得到诊断可能是一个部分,因为isalpha如何实现为宏。 在我的计算机上使用Glibc 2.27-3ubuntu1, isalpha定义为

     # define isalpha(c) __isctype((c), _ISalpha) # define __isctype(c, type) \ ((*__ctype_b_loc ())[(int) (c)] & (unsigned short int) type) 

    宏包含一个不幸的强制转换为int ,这将使你的错误沉默!


我在这么多人之后发布这个答案的一个原因是你没有修复代码 ,它仍然会受到未定义的行为的影响,因为扩展字符和char被签名(在x86-32和x86-上通常都是这种情况) 64)。

isalpha的正确参数是(unsigned char)argv[1][k] ! C11 7.4 :

在所有情况下,参数都是一个int ,其值应表示为unsigned char或者等于宏EOF的值。 如果参数具有任何其他值,则行为未定义。

我觉得很奇怪程序导致分段错误甚至没有打印TEST

printf不会立即打印,但会写入临时缓冲区。 如果要将其刷新为实际输出,请使用\n结束字符串。

用argv [1] [k]替换argv [1]解决了这个问题。

isalpha旨在使用单个字符。

首先,符合标准的编译器必须在此处为您提供诊断消息。 不允许从指针隐式转换为isalpha期望的int参数。 (这违反了简单分配规则,6.5.16.1。)

至于为什么不打印“TEST”,可能只是因为没有刷新stdout。 你可以尝试添加fflush(stdout); 在printf之后,看看这是否解决了这个问题。 或者,在字符串的末尾添加换行符\n

否则,只要没有副作用,编译器就可以自由地重新排序代码的执行。 也就是说,允许在printf("TEST");之前执行整个循环printf("TEST"); ,只要它打印TEST之前它可能会打印"Enter only alphabets!" 。 这种优化可能不会在这里发生,但在其他情况下它们可能会发生。