This problem depends heavily on the average file size. On the other hand, a simple computative trade-off can be made.
Checking file size
The easiest thing to check is the file size. We can compute this on a POSIX-compliant machine with A C standard library installed.
We can get the file info from the filename with the stat function.
#include <sys/stat.h>
// ...
struct stat st;
stat(filename, &st);
Then just access the file size with st_size.
printf("%d\n", st.st_size);
Optional: Checking File Permissions
If you need to, you can check the file permissions with st_mode.
st.st_mode
Size Problem
Depending on the size of the file, it may be better to actually compare without a checksum. For medium/large files, you can use a speedy CRC implementation (this is just one I typed up):
uint32_t crc32c(uint32_t crc, const unsigned char *buf, size_t len) {
int q;
crc = ~crc;
while (len--) {
crc ^= *buf++;
for (q = 0; q < 8; q++)
crc = crc & 1 ? (crc >> 1) ^ 0x82f63b78 : crc >> 1; // CRC iSCSI
}
return ~crc;
}
This CRC implementation is relatively standard. Depending on the file size as well, the hash function may be different. This implementation DOES not contain a lookup table, which you would want.
Note: For x86 systems, crc = (crc >> 1) ^ (0x82f63b78 & (0 - (crc & 1))) is slightly faster. Both are sound.
For a much faster tableless algorithm (credits to Hagai Gold and Stephen Brumme):
uint32_t crc32_1byte_tableless(const void* data, size_t length, uint32_t previousCrc32)
{
uint32_t crc = ~previousCrc32; // same as previousCrc32 ^ 0xFFFFFFFF
const uint8_t* current = (const uint8_t*) data;
while (length-- != 0)
{
uint8_t s = uint8_t(crc) ^ *current++;
// Hagai Gold made me aware of this table-less algorithm and send me code
// polynomial 0xEDB88320 can be written in binary as 11101101101110001000001100100000b
// reverse the bits (or just assume bit 0 is the first one)
// and we have bits set at position 0, 1, 2, 4, 5, 7, 8, 10, 11, 12, 16, 22, 23, 26
// => those are the shift offsets:
//crc = (crc >> 8) ^
// t ^
// (t >> 1) ^ (t >> 2) ^ (t >> 4) ^ (t >> 5) ^ // == y
// (t >> 7) ^ (t >> 8) ^ (t >> 10) ^ (t >> 11) ^ // == y >> 6
// (t >> 12) ^ (t >> 16) ^ // == z
// (t >> 22) ^ (t >> 26) ^ // == z >> 10
// (t >> 23);
// the fastest I can come up with:
uint32_t low = (s ^ (s << 6)) & 0xFF;
uint32_t a = (low * ((1 << 23) + (1 << 14) + (1 << 2)));
crc = (crc >> 8) ^
(low * ((1 << 24) + (1 << 16) + (1 << 8))) ^
a ^
(a >> 1) ^
(low * ((1 << 20) + (1 << 12) )) ^
(low << 19) ^
(low << 17) ^
(low >> 2);
// Hagai's code:
/*uint32_t t = (s ^ (s << 6)) << 24;
// some temporaries to optimize XOR
uint32_t x = (t >> 1) ^ (t >> 2);
uint32_t y = x ^ (x >> 3);
uint32_t z = (t >> 12) ^ (t >> 16);
crc = (crc >> 8) ^
t ^ (t >> 23) ^
y ^ (y >> 6) ^
z ^ (z >> 10);*/
}
return ~crc; // same as crc ^ 0xFFFFFFFF
}
Technically you can create a lookup table that is hilariously large on most flash memory chips (up to 4 GB), it's a logarithmic tradeoff.
For extremely large files, i.e terabytes, it may be beneficial to use xxHash.
Raw computation
If the average file size is (heuristically computed) below 52 bytes, you may benefit from the manual comparison. I will not provide a comparison C code as this post is relatively long.
Conclusion
The two (or three) step process is as follows:
- Compare file sizes
- Hash and check with first hash