Tutte e 3 modificano i dati, poichè i puntatori si passano solo per riferimento.
Se vuoi che il puntatore non venga modificato puoi
[list=a][*]Creare un puntatore alla struttura, lo allochi, fai un memcpy e usi quello per contare (e dopo lo elimini, ovviamente)[*]Crei un puntatore temporaneo in cui memorizzi il primo elemento della lista. Scorri la lista per contare, e dopo riassegni il puntatore a quello temporaneo per farlo tornare allo stato iniziale[/list=a]

In pratica
Esempio 1

codice:
typedef struct lista_ele
{
int ele;
lista_ele *prox;
} lista;

int conta(lista *pun)
{
  int i=0;
  lista *tmp = malloc(sizeof(lista)); //Non sono molto sicuro
  memcpy(pun,tmp,sizeof(lista));   //Che qui vada bene
  if (tmp->Prox != NULL)
   {
     i++; tmp=tmp->Prox;
   }
     free(tmp);
     return i;
}
Modo 2...


codice:
typedef struct lista_ele
{
int ele;
lista_ele *prox;
} lista;

int conta(lista *pun)
{
  int i=0;
  lista *tmp = malloc(sizeof(lista)); //Non sono molto sicuro
  memcpy(lista,tmp,sizeof(lista));

  if (pun->Prox != NULL)
   {
     i++; pun=pun->Prox;
   }
     memcpy(tmp,pun,sizeof(lista));
      free(tmp);
     return i;
}
L'ho pensato al volo quindi potrebbe anche essere tutto sbagliato