1

First of all, I know triple and quadruple pointers are bad practice and are ugly, that's not the point of this question, I'm trying to understand how they work. I'm aware using a struct would be much better.

I am trying to write a function that does some memory operations using memmove() and memcpy() on triple and double pointers that are passed-by-reference (or the C version of that). My memmove() works fine, but the memcpy() yields a SIGSEGV. Here's a minimal example

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

#define UNDO_DEPTH 25


void boardSave(int ***board, int game_sz, int ****history) {
    // Shift history to the right
    memmove(*history + 1, *history, (UNDO_DEPTH - 1) * sizeof(**history));
    // Copy board into history
    for (int row = 0; row < game_sz; ++row) {
        memcpy((*history)[0][row], (*board)[row], game_sz * sizeof((**board)[row]));
    }
}

int main(){
    // Game
    int game_sz = 5;
    // Allocate array for the board
    int **board = calloc(game_sz, sizeof(int *));
    for (int i = 0; i < game_sz; ++i) board[i] = calloc(game_sz, sizeof(int));
    // Allocate array for the history
    int ***history = calloc(UNDO_DEPTH, sizeof(int **));
    for (int i = 0; i < UNDO_DEPTH; ++i) {
        history[i] = calloc(game_sz, sizeof(int *));
        for (int j = 0; j < game_sz; ++j) {
            history[i][j] = calloc(game_sz, sizeof(int));
        }
    }
    board[0][0] = 1;
    boardSave(&board, game_sz, &history);
}

The objective of boardSave() here is to copy board onto history[0]. What am I doing wrong? Why is this causing a segmentation fault?

Bernardo Meurer
  • 2,295
  • 5
  • 31
  • 52
  • 2
    The different depths of indirection in `memmove(**history + 1, *history,`... is rather suspicious. – aschepler Apr 14 '17 at 00:08
  • @aschepler My intent with that is to move all elements of `history` 1 "slot to the right" eliminating the last (in this case 25th) element. – Bernardo Meurer Apr 14 '17 at 00:10
  • `memmove(**history + 1, **history, (UNDO_DEPTH - 1) * sizeof(***history));` seems to fix it. Need to text more – Bernardo Meurer Apr 14 '17 at 00:14
  • Nope, although the `memmove` was also incorrect. I have edited the original question. Thanks @aschepler. – Bernardo Meurer Apr 14 '17 at 00:15
  • 1
    Note that calling someone a [Three Star Programmer](http://c2.com/cgi/wiki?ThreeStarProgrammer) is not a term of approval. Four star programmers are likely to be even less popular. My mind is blown; I don't want to maintain your code. – Jonathan Leffler Apr 14 '17 at 00:21
  • Why do you have so many redundant casts – M.M Apr 14 '17 at 00:23
  • @JonathanLeffler Did you read my first paragraph? – Bernardo Meurer Apr 14 '17 at 00:25
  • @M.M I'm learning, how can I improve? – Bernardo Meurer Apr 14 '17 at 00:25
  • 2
    Yes; I'm letting anyone else who comes along and discovers this know that you're in deep waters and problems are not very surprising. You're better off not knowing, IMO. – Jonathan Leffler Apr 14 '17 at 00:26
  • @JonathanLeffler Cheers. To anyone out there, do **not** try and do this. – Bernardo Meurer Apr 14 '17 at 00:27
  • The first memmove is wrong. The `history` array (in main) is an array of pointers, each of which points to a separate allocation block (every board in the history is in its own allocation block). You seem to be trying to shuffle contents of all those separate blocks via a single memmove (clearly impossible). You could memmove the `history` array itself, and in that case you'd want to "rotate" the one you push off the end back onto the start so you don't have to reallocate memory. – M.M Apr 14 '17 at 00:39
  • Re. the redundant casts, you could remove all of the casts in your code – M.M Apr 14 '17 at 00:39
  • @M.M I meant to ask where am I being redundant, and why? Does "rotating" involve using two `memmove()`? – Bernardo Meurer Apr 14 '17 at 00:41
  • All of the casts are redundant, because the code behaves exactly the same without them and they do not have any clarity or safety benefit. To "rotate" by one, you could save the last one, do one memmove, then store the last one on the front. (Like the usual way you swap two values, but one part of the swap is a big memmove) – M.M Apr 14 '17 at 00:43
  • @M.M There's a requirement from the main code I am working on that all of the memory in `board` and `history` is zeroed upon allocation. – Bernardo Meurer Apr 14 '17 at 00:44
  • @BernardoMeurer casts don't zero memory ... – M.M Apr 14 '17 at 00:44
  • @M.M Oh, I thought you were referring to the `calloc()` calls, you mean the `(int **)` casts I suppose. – Bernardo Meurer Apr 14 '17 at 00:45
  • And the `(size_t)` ones, i.e. all of them – M.M Apr 14 '17 at 00:46
  • @M.M `size_t` casts are so GCC shuts up :P – Bernardo Meurer Apr 14 '17 at 00:46
  • You'll need to explain that one a bit more, the code with all casts removed should not elicit any warnings – M.M Apr 14 '17 at 00:47
  • @M.M Without `(size_t)` casts I get `warning: conversion to ‘long unsigned int’ from ‘int’ may change the sign of the result [-Wsign-conversion] memcpy((*history)[0][row], (*board)[row], game_sz * sizeof((**board)[row]));` – Bernardo Meurer Apr 14 '17 at 00:48
  • Stop using `-Wsign-conversion` , it "warns" for code that is perfectly fine and not in any way dangerous , you end up mangling your code for unclear benefit. I would even call it a compiler bug, since there is no possible way that converting a positive int to `long` may change its sign. Alternatively you could make `game_sz` be a `size_t` in the first place – M.M Apr 14 '17 at 00:51
  • @M.M Thanks for the advice, I'm taking the flag off. Could you show me how to perform the rotation? – Bernardo Meurer Apr 14 '17 at 00:52
  • OK i'll try to write an answer – M.M Apr 14 '17 at 00:54
  • @M.M I will update the example code one last time, without the casts – Bernardo Meurer Apr 14 '17 at 00:55

2 Answers2

3

In the main function you make history point to an array of UNDO_DEPTH pointers, each of which points to a board that has its own allocation. Since memmove moves a contiguous memory blocks, you cannot move the content of all those boards with memmove.

However, you could move down the pointers in that history array, leaving the board allocations untouched.

Just doing a single memmove would require you to free memory of the last board shuffled off, and allocate memory for the new board. But you could recycle that memory by moving the last pointer to the start instead.

Now, there is no need to pass the addresses of board and history to the boardSave function. It just makes your code more complicated for no reason. The simpler version would be:

void boardSave(int **board, int game_sz, int ***history)
{
// Save the last board
    int ** last_board = history[UNDO_DEPTH - 1];

// Shuffle down all the boards
    memmove( &history[1], &history[0], (UNDO_DEPTH - 1) * sizeof history[0] );

// Put the old last board on the front
    history[0] = last_board;

// Copy board into front of history
    copy_board( game_sz, history[0], board );
}

// Put a prototype for this earlier in the code. I think it makes
// the boardSave function clearer to use a separate function for this
// operation, which you might end up using on its own anyway.
//
void copy_board( int game_sz, int **dest, int **src )
{
    for(int row = 0; row < game_sz; ++row)
        memcpy(dest[row], src[row], game_sz * sizeof dest[0][0]);
}

Personally I'd prefer to avoid memcpy in the last function and just write a simple loop that is obviously correct. The compiler will optimize it to use memcpy anyway, but without the possibility of making an error in the memcpy parameters:

    for(int row = 0; row < game_sz; ++row)
        for (int col = 0; col < game_sz; ++col)
            dest[row][col] = src[row][col];

Similar comments would apply to the use of memmove actually.

I would also make some use of const in the function signatures, so that a compiler error is generated if I accidentally switched the "dest" and "src" arguments. But I left that out at this stage for simplicitly.

In main the call would now be:

boardSave(board, game_sz, history);

If you reeeeealy want to pass pointers for practice then I would "de-point" them at the start of the function:

void complicated_boardSave(int ***p_board, int game_sz, int ****p_history)
{  
    int *** history = *p_history;
    int  ** board = *p_board;

    // rest of code the same
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Will this actually change the values held on the variables in `main`, even though I'm not passing-by-reference? – Bernardo Meurer Apr 14 '17 at 01:18
  • 1
    @BernardoMeurer you do not want to change the variables in main - you want to change the memory that those variables point to – M.M Apr 14 '17 at 01:21
  • Cannot thank you enough for the patience and work put into helping me understand this. Wish I could pay you a beer. – Bernardo Meurer Apr 14 '17 at 01:29
  • I'll take it if it's cold, if it's warm, you guys on the other side of the pond can keep it `:)` – David C. Rankin Apr 14 '17 at 03:42
  • I don't get the purpose of this line history[0] = last_board; history[0] will be soon re-written with board. I don't think you need the variable last_board; – Nguai al Apr 14 '17 at 06:40
  • @Nguaial it is necessary, history[0] is not re-written with board. The contents of memory pointed to by board will be copied into memory pointed to by history[0]. – M.M Apr 14 '17 at 06:57
0

I understand you want to challenge pointers. I wanted provide a solution that utilizes single pointer. As a matter of fact, you don't need to use a pointer at all.

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

const int game_sz = 5;
#define UNDO_DEPTH 25



void boardSave(int *board[game_sz], int game_sz, int *history[UNDO_DEPTH]
[game_sz]) 
{
    int i,j,k;

    for( i = 0; i < UNDO_DEPTH - 1; i++)
       for( j = 0; j < game_sz; j ++ )
          for( k = 0; j < game_sz; j ++ )
            history[i+1][j][k] = history[i][j][k];

    for( i = 0; i < game_sz - 1; i++)
       for( j = 0; j < game_sz; j++ )
           history[0][i][j] = board[i][j];
}

int
main(void)
{
  int *board[game_sz];
  int *history[UNDO_DEPTH][game_sz];
  int i, j;


  for (i = 0; i < game_sz; ++i) 
    board[i] = calloc(game_sz, sizeof(int));
  board[0][0] = 1;

  // Allocate array for the history
  for ( i = 0; i < UNDO_DEPTH; ++i)
      for ( j = 0; j < game_sz; ++j)
        history[i][j] = calloc(game_sz, sizeof(int));


  boardSave( board, game_sz, history);

  return 0;
 }
Nguai al
  • 958
  • 5
  • 15