【问题标题】:Strange behaviour then swapping values inside pointers奇怪的行为然后在指针内交换值
【发布时间】:2021-12-23 23:18:16
【问题描述】:

我有这段代码应该在 a_ptr 和 b_ptr 中交换两个值:

// swaps two values inside two variables of any type
void swap(void *a_ptr, void *b_ptr)
{
    size_t size = sizeof(void *);
    void *tmp = malloc(size);
    memcpy(tmp, a_ptr, size);
    memcpy(a_ptr, b_ptr, size);
    memcpy(b_ptr, tmp, size);
    free(tmp);

    // char wot0 = 0;
    // char wot1 = 0;
    // swap(&wot0, &wot1);
}

int main(){

    long a = -7;
    long b = 99999999;
    printf("A: %ld, B: %ld\n", a, b);
    swap(&a, &b);
    printf("A: %ld, B: %ld\n", a, b);
    printf("\n");

    short c = -9;
    short d = 11111;
    printf("C: %hd, D: %hd\n", c, d);
    swap(&c, &d);
    printf("C: %hd, D: %hd\n", c, d);
    printf("\n");

    char ca = 'a';
    char cx = 'x';
    printf("CA: %c  CX: %c\n", ca, cx);
    swap(&ca, &cx);
    printf("CA: %d  CX: %c\n", ca, cx);
    printf("\n");

    char *str0 = "Hello, ";
    char *str1 = "World!";
    printf("STR0: %s  STR1: %s\n", str0, str1);
    swap(&str0, &str1);
    printf("STR0: %s  STR1: %s\n", str0, str1);
    printf("\n");

    return 0;
}

但是输出是:

A: -7, B: 99999999
A: 99999999, B: -7

C: -9, D: 11111
C: -7, D: -9

CA: a  CX: x
CA: -9  CX: a

STR0: Hello,   STR1: World!
STR0: World!  STR1: Hello, 

它成功地交换了ab,然后以某种方式将c 替换为b,并将ca 替换为d,这怎么可能?

另外,取消注释这些行:

    // char wot0 = 0;
    // char wot1 = 0;
    // swap(&wot0, &wot1);

导致段错误,为什么?

编辑: 我想我没有很好地表达我的意图。我基本上想做的是交换指针,以便a_ptr 指向b 内部的值,b_ptr 指向a 内部的值,我不想实际复制这些值本身,我想我实现了这有点成功,不同长度的字符串(例如"Foo""Hello, World!")被交换而没有任何问题,我测试了这一点,但是我不明白为什么有些变量没有被交换并且实际上指向之外的值我传递给函数的

【问题讨论】:

  • 要交换的目标可以是char,也可以是double,除非明确告知,否则函数c不知道传递memcpy的大小。
  • 越界访问的未定义行为。
  • @WeatherVane 什么? sizeof(void*) 原则上完美可以,根本不是编译器扩展。
  • 你想改变swap()的参数指向的对象的值吗?那么sizeof( void * ) 不是要复制的正确字节数,因为您想复制参数指向的内容——longshortchar——你没有认识的方式。还是您想自己更改指针(地址)?这也不起作用,因为指针本身是按值传递的,而您的swap() 不能使调用者的a 引用b,反之亦然。
  • @WeatherVane 我建议删除第一条评论,因为在您编辑后它仍然完全错误。

标签: c pointers


【解决方案1】:

sizeof(void *); 是一个常数(通常为 4 或 8),不会为您提供它所指向的对象的大小。当您复制 size 字节时,您没有复制所用类型的正确数量。

向函数提供类型的大小可能会更好:

// swaps two values inside two variables of any type
void swapper(void *a_ptr, void *b_ptr, size_t size)
{
    void *tmp = malloc(size);
    memcpy(tmp, a_ptr, size);
    memcpy(a_ptr, b_ptr, size);
    memcpy(b_ptr, tmp, size);
    free(tmp);
}

// Generate compilation error if objects of different sizes are used.
// The false switch case (0) can only be defined once so if the sizes
// are not the same, it'll try to redefine "case 0" and fail compiling:
#define compile_assert(_expr) switch (_expr) { case 0: break; case _expr: break; } 

#define swap(x,y) do { \
    compile_assert(sizeof(*(x)) == sizeof(*(y))); \
    swapper((x),(y),sizeof(*(x))); } while (0)

按照你的目标来称呼它:

swap(&a, &b);

如果您只需要交换基本类型,则可以为所有这些类型进行不同的实现。这也应该使它更安全,因为以这种方式提供指向不同类型对象的指针更加困难:

#define SwapBuilder(name,type) \
    void name(type *a, type *b) { type tmp = *a; *a = *b; *b = tmp; }
SwapBuilder(swap_char,char)
SwapBuilder(swap_schar,signed char)
SwapBuilder(swap_uchar,unsigned char)
SwapBuilder(swap_short,short)
SwapBuilder(swap_ushort,unsigned short)
SwapBuilder(swap_int,int)
SwapBuilder(swap_uint,unsigned int)
SwapBuilder(swap_long,long)
SwapBuilder(swap_ulong,unsigned long)
SwapBuilder(swap_longlong,long long)
SwapBuilder(swap_ulonglong,unsigned long long)
SwapBuilder(swap_float,float)
SwapBuilder(swap_double,double)
SwapBuilder(swap_longdouble,long double)

// A _Generic to call the correct function:
#define swap(x,y) _Generic((x), \
    char* : swap_char, \
    signed char* : swap_schar, \
    unsigned char* : swap_uchar, \
    short* : swap_short, \
    unsigned short* : swap_ushort, \
    int* : swap_int, \
    unsigned int* : swap_uint, \
    long* : swap_long, \
    unsigned long* : swap_ulong, \
    long long* : swap_longlong, \
    unsigned long long* : swap_ulonglong, \
    float* : swap_float, \
    double* : swap_double, \
    long double* : swap_longdouble \
)((x),(y))

您仍然可以使用swap(&a, &b); 调用它

【讨论】:

  • @EOF 和 chux:是的,我知道。在将void* 发送到函数之前进行类型检查会很棒。如果只需要支持基本类型,也许可以将其制成 _Generic 调用 swap 函数的类型安全版本。
  • 我最喜欢你的第一个解决方案。添加“更高级”宏:#define compile_assert(_expr) switch (_expr) { case 0: break; case _expr: break; }#define swap(x,y) do { compile_assert(sizeof(*(x)) == sizeof(*(y))); swapper((x),(y),sizeof(*(x))); } while (0) 对我来说,这是大小安全的 [如果 not 类型安全] 和 more 通用(我们可以改进typeof)。此外,在swapper 中,为了提高速度,我们可以使用 (1): alloca 而不是malloc/free 用于大多数 用法。或者,(2):static void *tmp; static size_t tmplen; 并执行 if (size > tmplen) { tmp = realloc(tmp,size); tmplen = size; }
  • @CraigEstey alloca() 替代方案:VLA unsigned char void tmp[size];(标准 - 可选,但没有错误检查)
  • @chux-ReinstateMonica X-mas 模式。 :-) 没错,这张桌子并不像我说的那么完整。昨天我开始考虑使用default: 回退到swapper,但完成它很累。也许我会回到这个并稍后更新它。
  • 你是对的。因此,如果除了类型匹配之外,我们还添加了 sizeof 的断言,我们可能会更接近。我试图使示例保持简短,但我最初使用了两者。
猜你喜欢
  • 1970-01-01
  • 2020-01-22
  • 2014-12-17
  • 2020-11-28
  • 2021-07-01
  • 2013-03-31
  • 2019-04-27
  • 2012-05-29
  • 1970-01-01
相关资源
最近更新 更多