【问题标题】:Is there any way of protecting a variable for being modified at runtime in C?有什么方法可以保护在 C 中运行时修改的变量吗?
【发布时间】:2015-06-15 12:35:10
【问题描述】:

我想知道是否有任何方法可以保护变量在初始化后被修改(类似于在运行时“常量化”变量)。例如:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int v, op;
    scanf( "%d", &op );
    if( op == 0 )
        v = 1;
    else
        v = 2;
// here transform v to a constant...
    .
    .
// ...and that any attempt to modify v yields to an error.
    .
    .
    return EXIT_SUCCESS;
}

【问题讨论】:

  • C 借用了 C++ const 关键字。
  • @DavidHaim C 在 C++ 发明之前指定了 const 关键字
  • 可以像example 那样保护堆内存。

标签: c variables constants


【解决方案1】:

您可以像这样使输入的结果为const

int func()
{
    int op = 0;
    scanf( "%d", &op );
    if( op == 0 )
        return 1;
    else
        return 2;
}

int main()
{
    const int v = func();
    // ...
}

注意。当然,没有办法防止在程序后期发生未定义的行为,这似乎改变了v(因为根据定义,未定义的行为可以产生任何影响)。

【讨论】:

  • 我测试了你的代码。它工作正常。所以指令 const int v = func();在运行时而不是在编译时执行。如果添加了尝试修改 v 的行,则会报告错误(使用 gcc 编译)。
【解决方案2】:

是的:考虑您的代码:

foo.h:

void do_something(int arg);

foo.c:

#include "foo.h"

void do_something(int arg)
{
    // implement
    //
    // Here "arg" is completely under your control.
}

foo_test.c:

#include "foo.h"

// add unit tests for do_something

ma​​in.c:

#include <stdio.h>
#include <stdlib.h>
#include "foo.h"

int main(void)
{
    int op;
    if (scanf("%d", &op) != 1) { abort(); }

    do_something(op == 0 ? 1 : 2);
}

现在从代码中可以完全清楚地看到,do_something 的函数参数是函数的私有和本地的,不受调用之上的任何内容的影响。

【讨论】:

  • @Olaf 这不是“丢失”,而是不需要。 extern 对于函数声明是可选的,如果没有指定存储类,则默认具有外部链接。
  • @Olaf 我不明白为什么。 foo.h 包含在 main.c 中,它将提供 do_something() 的原型(默认为外部),foo_test.c 将提供定义。
  • @Olaf 如果你有一个先有外部链接然后有内部链接的声明,那么它是未定义的行为。我不明白extern 关键字的存在会在那里解决什么问题。
  • @Olaf:您能否在其他地方讨论extern 的优点?
【解决方案3】:

防止全局变量在初始化后被修改的唯一方法是声明它const

const int i = 5;

但是,如上所示,这需要使用定义对其进行初始化。除了在编译时求值的常量表达式之外,没有办法从程序代码初始化它。

请注意,虽然您可以将 const 丢弃,但这会导致 undefined bahaviour 并且很可能会导致问题,因为 const 变量可能会被放入 read-只有你的程序的内存区域。

如果您确实需要对此类变量进行访问控制,则必须将其作为 static 放入函数中(在第一次调用时进行初始化),或者 - 最好是单独的模块/编译单元(也称为static)。然后在此使用显式 setter/getter 函数:

"wrapper.h":

#ifndef WRAPPER_H
#define WRAPPER_H

extern void set_once(int v);
extern int get_value(void);

#endif

"wrapper.c":

#include <stdbool.h>
#include "wrapper.h"

static struct {
    bool is_set;    // guaranteed false on startup
    int value;
} my_var;

void set_once(int v)
{
    if ( !my_var.is_set )
        my_var.value = v;
    else
        ; // report an error, e.g. abort()
}

int get_value(void)
{
    if ( !my_var.is_set )
        return my_var.value;
    // report an error, e.g. abort()
}

"main.c"

#include "wrapper.h"

int main(void)
{
    set_once(5);
    int i = get_value();
}

这样,如果您使用未初始化的值或尝试多次设置它,您将收到运行时错误。请注意,该标志依赖于将全局变量初始化为 0(保证评估 false)。 尽管您可能只是忽略了多个集合,但至少在调试/测试期间(例如使用 assert)是捕获和报告的好习惯。

编辑:

上述方法适用于全局变量。如果要保护多个变量,可以对其进行修改,例如函数采用指向结构的指针。如果要使用不同的类型,请将标志打包到自己的结构中,并将其作为第一个匿名字段添加到每个要保护的类型的结构类型中。如果 gcc 扩展可用且允许,请查看 -fplan9-extensions。使用不透明指针可以避免意外的外部修改。

但是,对于局部变量,请使用 @MattMcNabb 的版本。

【讨论】:

  • 请看一下 Matt McNabb 的代码。在那里, v 的值是在运行时分配的。是否有任何编译选项将其检测为错误?还是根本没有错误?
  • @jruiz:我在答案中添加了“全球”一词。马特的回答实际上确实有效,但仅适用于auto 变量,甚至不适用于本地static。我确实认为它应该是一个全局的(编译单元或程序范围),所以我提供了一个更通用的方法。如果您只需要auto,他的方法对您来说会更好(好吧,您也可以将指针传递给所有被调用的函数,但这很快就会变得难以管理。
  • @jruiz:如果您有多个此类变量,您可以通过将指向此类变量的指针传递给函数来将此方法扩展到更通用的版本。实际上没有必要为每个变量设置一组。如果允许 gcc 扩展,甚至可以为不同的类型(几乎)做到这一点,而无需类型转换。
  • 感谢您的解决方案,但是当我使用 -pedantic 标志编译您的代码时,编译器会报告错误:ISO C90 禁止混合声明。另一个细节是在你的代码中 my_var 是一个全局变量,我正在避免。
  • @jruiz:好吧,我没有编译。可能有错别字,告诉我哪里,我会改正的。对于 C90:代码使用成为 C99 标准的“stdbool.h”。我通常不关心 C90 支持(如果需要,我会使用 C11)。但是,这只是我main() 的示例。只需将定义移到块上并将分配留在原处即可。毕竟这只是一个例子。关于全球:我在之前的评论中已经对此发表了评论。请注意,该变量只有 CU 范围,而不是程序全局。
【解决方案4】:

不,从来没有。

你可以让它const,但这更像是一个轻微的提示,几乎不费吹灰之力就可以丢弃。此外,如果你有一个指向某个东西的指针,你总是可以通过直接内存访问来改变它。

您也可以将它隐藏在一个管理对它的写访问的类中,但您的情况与第一种情况相同——您有一个指向它的指针。

【讨论】:

  • 这不仅仅是一个提示,然后可以将对象放入不可修改的内存中,并且丢弃限定符会使您的程序不严格符合。
  • const 是程序员的承诺,该值不会改变,这不仅仅是一个提示。编译器的优化器会相信你的话,并且可能会生成假定值不变的代码。
  • @Blindy,你看到 Matt McNabb 发送的代码了吗?你看到那里有什么错误吗?
  • @jruiz,没有错误,但你误解了它。您可以轻松地获取 v 的指针(或者甚至只是 esp/rsp 的原始值并减去 4)并更改它,而无需更改 const 的定义(或将其丢弃,这也可以)。这就是 gcc/msvc/icc 优化选项中所有那些未命名的别名警告的含义。例如,将其与 C# 进行对比,其中 const 变量甚至从未写入代码,它们的文字值被替换为使用它们的操作码。
  • 我坚持认为,与大多数人对 const 的期望相比,这是一个温和的提示,实现甚至不需要遵守(除了明显拒绝直接更改实际具有const 附加到它们的修饰符),事实上,一旦你用它做一些时髦的事情,它们实际上就会放弃它,比如使用 &amp; 运算符。
猜你喜欢
  • 2022-12-30
  • 1970-01-01
  • 2011-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-04
  • 2016-03-28
  • 1970-01-01
相关资源
最近更新 更多