提问人:Thomas Matthews 提问时间:8/11/2023 最后编辑:Thomas Matthews 更新时间:8/11/2023 访问量:94
指向 2D 数组项的指针可以为 NULL 吗?
Can pointer to 2D array item be NULL?
问:
静态分析程序 (LDRA) 存在以下语句问题:
const float range_min = p_results_phase->range_min;
分析器表示需要测试指针是否为 NULL。p_results_phase
定义如下:
Cal_Ver_Results_t const * const p_results_phase = &results[test_num][CAL_VER_PHASE];
数组在函数声明中定义:results
void DrawVACalibrationVerificationResultsScreen(Cal_Ver_Results_t results[][CAL_VER_SI_COUNT], unsigned int test_num)
我的理解是指针不能为 NULL,因为指针被指定指向数组中的某个位置。p_results_phase
results
编辑 1:实际代码(按顺序):
Cal_Ver_Results_t const * const p_results_phase = &results[test_num][CAL_VER_PHASE];
const float range_min = p_results_phase->range_min;
const float range_max = p_results_phase->range_max;
问题:
- 如果变量超出范围,指针是否会分配给 NULL?
p_results_phase
test_num
- 如果 超出范围,指针是否会分配给 NULL?
p_results_phase
CAL_VER_PHASE
定义:Cal_Ver_Results_t
typedef struct Cal_Ver_Results_s {
float result1;
float result2;
float range_min;
float range_max;
_Bool pass;
}Cal_Ver_Results_t;
答:
指针值是根据作为函数参数接收的数组的地址计算得出的。如果定义为局部或全局数组,并且假设元素位于数组边界内,则指针不能为 null,但尽管函数参数定义中有数组语法,但该指针仍作为指针传递,因此该函数可以接收 null 指针作为参数。在这种情况下,计算方式会生成一个无效的指针,取消引用它具有未定义的行为。results
results
p_results_phase
&results[test_num][CAL_VER_PHASE]
计算出的指针可能为 null,但这将是巧合:例如,如果 和 都是 和 是 null 指针。比较的价值有限,但检查不是空指针是更一般的健全性检查。test_num
CAL_VER_PHASE
0
results
p_results_phase
NULL
result
至于你的其他问题:
如果变量越界,指针是否会被分配给?
p_results_phase
NULL
test_num
不太可能,但在这种情况下将是一个无效的指针,不得取消引用。p_results_phase
如果 出界,指针会被分配给吗?
p_results_phase
NULL
CAL_VER_PHASE
同上,将是一个无效的指针。取消引用它将具有未定义的行为。指针可能指向数组中或数组外部的另一个条目,当您取消引用无效指针时,任何事情都可能发生。p_results_phase
当您确认这是数组的索引时,您可能需要检查它是否在正确的边界内,但未提供数组大小,因此您可能必须信任调用方。test_num
您可以非常简单地检查它不是空指针results
if (!results) {
/* signal some error */
return;
}
C99 引入了一种语法,用于指定使用数组语法声明的函数参数的最小元素数。例如,该函数可以声明和定义为:
void DrawVACalibrationVerificationResultsScreen(
Cal_Ver_Results_t results[static 10][CAL_VER_SI_COUNT],
unsigned int test_num)
{
...
}
这告诉编译器指向至少包含对象数组的数组。results
10
CAL_VER_SI_COUNT
Cal_Ver_Results_t
使用此原型,它不应允许显式传递 null 指针,如果它无法判断函数参数是否确实是预期最小长度的数组,则可能会产生警告。
同样,静态代码分析器应该能够假定它不能是空指针,因此两者都不是,前提是使用的索引值在二维数组边界内。results
p_results_phase
此功能尚未得到广泛接受,尤其是在面向嵌入式系统的开发人员中,这可能是由于编译器支持不佳。恕我直言,语法既繁琐又不直观:我不主张重写 的原型,以强调指针必须为非 null,更不用说那些 of 和 friends,它只会解决这些函数参数的约束之一。strlen
size_t strlen(const char s[static 1])
strcpy()
评论
test_num
void f(int x[42]);
void f(int *x);
a[i]
a+i
a
a[i]
a+i
答案是肯定的,如果你不走运的话。
特别是,您需要确保:
该参数不是 .虽然它在语法上声明为数组,但实际上它的行为与普通指针非常相似。
results
NULL
也就是说,正如你所说,在范围内。让它出界可能不会导致存在,那将是一个不幸的巧合,但它会指向任何地方并导致 UB。
test_num
p_result_phase
NULL
CAL_VER_PHASE
也应该有一个有效的值,但习惯上大写标识符是常量,所以它的值应该是OK(请检查)。
评论
results
越界访问数组是一个典型的问题。在 C 中,此行为是未定义的,因此变量可能为 NULL,也可能不是 NULL。p_results_phase
- 如果test_num变量越界,是否p_results_phase指针分配给 NULL?
是的,也不是。如果使用越界索引,则可能指向与预期不同的内存地址。例如:
被计算为 where 是数组的基址,是数组的索引。
现在,如果您正在访问越界索引,例如 ,它将只指向一个内存地址,如果该地址位于程序的虚拟地址空间内,那么您最终只会读取不正确的值,另一方面,写入这样的内存地址将覆盖该内存位置上存在的内容,这可能会导致其他严重问题。
此外,如果生成的内存地址位于程序虚拟地址空间的受限区域中,则对越界索引进行操作也可能导致分段错误。a[i]
*(a + i)
a
i
a[-100]
(a + (-100)) == (a-100)
如果指针p_results_phase是否被分配给 NULL CAL_VER_PHASE越界了?
同样的答案。越界数组索引意味着您正在读取/写入非预期的内存位置。对这些内存地址执行读/写操作将导致未定义的结果。
评论
a[i]
评估为 和 不是 .*(a + i)
(a + i)
a[-100]
(a + (-100)) == (a-100)
a[-100]
a-100
a-100
a-100
我的理解是指针不能为 NULL,因为指针被指定指向数组中的某个位置。
p_results_phase
results
嗯,是的,但更一般地说,是用一元运算符中的表达式的结果初始化的。只要操作数是有效的左值,操作就会计算其地址,该地址需要与每个 null 指针进行比较。如果操作数不是有效的左值,那么与其说是操作可能产生空指针的问题,不如说是操作具有未定义行为的问题。添加 null 检查不会以任何方式解决这个问题。p_results_phase
&
&
特别
- 如果变量超出范围,指针是否会分配给 NULL?
p_results_phase
test_num
- 如果 超出范围,指针是否会分配给 NULL?
p_results_phase
CAL_VER_PHASE
C 语言规范没有回答这两个问题。尝试通过越界索引引用数组元素会产生未定义的行为。在实践中,该 UB 不太可能表现为被计算为 null 指针的地址,但它可以。然而,这在很大程度上是无关紧要的,因为未定义的行为不是本地化的。如果存在越界访问,则这本身就是问题所在,而不是生成的 UB 允许程序表现得好像计算的指针值为 null。
分析器表示需要测试指针是否为 NULL。
p_results_phase
分析器错误。但是,如果您需要安抚它,那么您可能需要添加无用的 null 检查。更有可能的是,您的编译器比分析器更智能,并且在启用优化的情况下进行编译时会删除额外的检查。
或者,可能有一种方法(例如,特殊构造的注释)来指示分析器忽略此感知到的问题。如果是这样,这可能值得考虑,特别是如果你的编译器碰巧警告 null 检查是无关紧要的,这是可以想象的。
静态分析工具通常是关于应用/执行编码标准,而不是直接检测错误。编码标准的目的是减少维护和重用中的错误。
在这种情况下,在取消引用任何指针之前测试 null 是一种编码标准。它允许代码在其他使用上下文或维护中保持可靠,其中在其他地方所做的更改可能会导致指针为 null。
您可以从分析配置文件中删除该规则,也可以使用工具使用的任何消息抑制注释格式禁止显示此特定实例。
评论