C99中strtol(str, &str, 10)是否违反restrict别名规则?
先看这段代码:
char *str = "1234"; long n = strtol(str, &str, 10);
C99中strtol()的原型为:
long strtol(const char *restrict nptr, char **restrict endptr, int base);
我的问题是:该调用是否违反restrict约定并产生未定义行为?我曾见过类似问题,但现有回答缺乏足够解释,无法消除我的疑惑。
我的分析如下
const char *restrict nptr向编译器承诺:nptr是访问字符串"1234"所在内存区域的唯一途径。char **restrict endptr向编译器承诺:endptr是访问变量str的唯一途径。
调用时的假设场景:
- 字符串"1234"位于地址
0x100 - 变量str位于地址
0x500
此时:
nptr=0x100(指向字符串)endptr=0x500(指向变量str)
这两个指针本身指向不同的内存位置,因此从表面看nptr和endptr直接遵守了restrict约束。
但需要注意,nptr和*endptr指向同一地址。当strtol写入*endptr = ...时,它仅修改变量str,并未直接访问字符串"1234"。只有当strtol()通过**endptr进行双重解引用时,才会通过endptr访问该字符串。
如果strtol()的某个实现出于任何原因通过**endptr访问字符串"1234",那么调用strtol(str, &str, 10)时,nptr的restrict承诺将被违反——因为该字符串将通过nptr以外的指针被访问。
具体问题
根据C99的§6.7.3.1,这是否构成restrict违规?具体而言:
endptr上的restrict仅约束endptr本身,还是也约束*endptr与nptr的关系?- 即使endptr和nptr本身不同,若
*endptr与nptr指向同一内存,是否足以产生未定义行为? - 若某个假设的
strtol()实现通过**endptr访问字符串"1234",是否会被视为不符合C99标准?
问题解答
1. endptr的restrict约束范围
char **restrict endptr中的restrict仅约束指针endptr本身的访问:它承诺在strtol的执行期间,endptr指向的内存(即变量str的地址)只能通过endptr来访问,不能通过其他指针别名修改或读取这个内存位置。
这个restrict修饰符不会直接约束*endptr(也就是str变量的值,即指向字符串的指针)与nptr的关系——restrict的约束是针对它所修饰的指针的指向对象,而非指针指向对象的指向对象。
2. *endptr与nptr指向同一内存是否触发未定义行为?
不会直接触发。根据C99 §6.7.3.1的规则,restrict的核心约束是:如果两个restrict指针指向的内存区域存在重叠,那么在函数执行期间,不能通过其中一个指针修改内存,同时通过另一个指针访问该内存。
在这个调用场景中:
- nptr指向的是字符串常量"1234"的内存(只读),
strtol不会修改这个区域; - endptr指向的是变量str的内存,
strtol会修改*endptr(即str的值),但这个修改操作和nptr指向的字符串内存无关——因为修改的是str变量本身,而非字符串内容。
只有当strtol通过**endptr去访问nptr指向的字符串时,才会违反nptr的restrict承诺:因为此时字符串内存被同时通过nptr和*endptr(即修改前的str)访问了。但只要strtol的实现不做这种操作,就不存在违规。
3. 通过**endptr访问字符串的strtol实现是否符合标准?
不符合。C99标准对strtol的行为有明确要求:它应当通过nptr来扫描输入字符串,而endptr的作用仅用于存储第一个无法转换的字符的地址。标准中没有任何允许strtol通过**endptr访问输入字符串的条款,这种实现属于超出标准规范的行为,是不符合C99标准的。
实际上,所有符合标准的strtol实现都会仅通过nptr来访问输入字符串,不会去解引用endptr指向的指针(即*endptr)来访问字符串,因此你的调用方式在符合标准的实现中是安全的,不会触发未定义行为。
内容的提问来源于stack exchange,提问作者Parminder Singh

