我有以下程序会导致分段错误。
#include <stdio.h>
#include <string.h>
#include <ctype.h>
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])) {
用CCD_ 2代替CCD_。
然而,我发现很奇怪的是,该程序甚至没有打印TEST
就导致了分段故障。我还预计isalpha
函数会错误地检查char*
指针的下字节是否指向argv[1]
,但事实似乎并非如此。我有代码来检查参数的数量,但为了简洁起见,这里没有显示。
这里发生了什么?
一般来说,讨论为什么未定义的行为会导致这种或那种结果是毫无意义的。
但是,即使不在规范范围内,试图理解为什么会发生一些事情也没有坏处
isalpha
的实现使用一个简单的数组来查找所有可能的unsigned char
值。在这种情况下,作为参数传递的值将用作数组的索引。虽然实数字符被限制为8位,但整数不是。该函数以一个int
作为参数。这也是为了允许进入不适合argv[1]
1的EOF
。
如果您将0x7239482342这样的地址传递到函数中,则这远远超出了所述数组的末尾,并且当CPU尝试读取具有该索引的条目时,它会从世界的边缘掉下来。)
用这样的地址调用isalpha
是编译器应该在将指针转换为整数时发出一些警告的地方。你可能忽略了。。。
库可能包含检查有效参数的代码,但它也可能只是依赖于用户不传递不应传递的内容。
printf
未冲洗- 从指针到整数的隐式转换本应至少生成约束违反的编译时诊断,但却生成了超出CCD_ 14范围的数字。
isalpha
被实现为查找表,这意味着您的代码访问该表超出了界限,因此是未定义的行为 -
为什么没有得到诊断,部分原因可能是
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通常都是这种情况)。
给argv[1][k]
0的正确参数是(unsigned char)argv[1][k]
!C11 7.4:
在所有情况下,自变量都是
int
,其值应表示为unsigned char
或等于宏EOF
的值如果参数有任何其他值,则行为是未定义的
我觉得很奇怪,程序甚至没有打印TEST 就导致了分段故障
printf
不会立即打印,但它会写入临时缓冲区。如果要将字符串刷新为实际输出,请使用n
结束字符串。
并将argv[1]替换为argv[1][k]解决了问题。
isalpha
用于处理单个字符。
首先,一个合格的编译器必须在这里给您一个诊断消息。不允许从指向isalpha
期望的int
参数的指针隐式转换。(这违反了简单分配规则,6.5.16.1。)
至于为什么没有打印"TEST",可能只是因为stdout没有被刷新。您可以尝试在printf之后添加fflush(stdout);
,看看这是否解决了问题。或者,在字符串的末尾添加换行n
。
否则,只要没有副作用,编译器就可以自由地重新排序代码的执行。也就是说,允许在printf("TEST");
之前执行整个循环,只要它在可能打印"Enter only alphabets!"
之前打印TEST
即可。这种优化在这里可能不太可能发生,但在其他情况下也可能发生。